Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 350 lines (276 sloc) 9.215 kb
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
1 //
2 // KSHTMLWriter.m
3 // Sandvox
4 //
5 // Created by Mike on 23/02/2010.
6 // Copyright 2010 Karelia Software. All rights reserved.
7 //
8
9 #import "KSHTMLWriter.h"
10
11
12 @implementation KSHTMLWriter
13
72adfa1 @mikeabdullah Documenting .isXHTML generation better.
mikeabdullah authored
14 #pragma mark Creating an HTML Writer
15
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
16 - (id)initWithOutputWriter:(id <KSWriter>)stream;
17 {
18 [super initWithOutputWriter:stream];
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
19
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
20 _isXHTML = YES;
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
21 _classNames = [[NSMutableArray alloc] init];
22
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
23 return self;
24 }
25
26 - (id)initWithOutputWriter:(id <KSWriter>)stream isXHTML:(BOOL)isXHTML;
27 {
28 if (self = [self initWithOutputWriter:stream])
29 {
30 _isXHTML = isXHTML;
31 }
32
33 return self;
34 }
35
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
36 - (void)dealloc
37 {
38 [_classNames release];
39
40 [super dealloc];
41 }
42
72adfa1 @mikeabdullah Documenting .isXHTML generation better.
mikeabdullah authored
43 #pragma mark XHTML
44
45 @synthesize XHTML = _isXHTML;
46
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
47 #pragma mark Document
48
49 - (void)startDocument:(NSString *)DTD isXHTML:(BOOL)isXHTML;
50 {
51 _isXHTML = isXHTML;
52 [self startDocument:DTD];
53 }
54
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
55 #pragma mark CSS Class Name
56
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
57 - (void)pushElementClassName:(NSString *)className;
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
58 {
59 [_classNames addObject:className];
60 }
61
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
62 - (NSString *)elementClassName;
ecfabe0 @mikeabdullah Expose -className.
mikeabdullah authored
63 {
64 NSString *result = nil;
65 if ([_classNames count])
66 {
67 result = [_classNames componentsJoinedByString:@" "];
68 }
69 return result;
70 }
71
e96c6c8 @mikeabdullah Calling [htmlWriter pushElementAttribute:@"class" value:@"foo"] automati...
mikeabdullah authored
72 - (void)pushElementAttribute:(NSString *)attribute value:(NSString *)value;
73 {
74 if ([attribute isEqualToString:@"class"])
75 {
76 [self pushElementClassName:value];
77 }
78 else
79 {
80 [super pushElementAttribute:attribute value:value];
81 }
82 }
83
589179c @mikeabdullah Include -className in -elementAttributes.
mikeabdullah authored
84 - (NSDictionary *)elementAttributes;
85 {
86 id result = [super elementAttributes];
87
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
88 NSString *class = [self elementClassName];
589179c @mikeabdullah Include -className in -elementAttributes.
mikeabdullah authored
89 if (class)
90 {
91 result = [NSMutableDictionary dictionaryWithDictionary:result];
92 [result setObject:class forKey:@"class"];
93 }
94
95 return result;
96 }
97
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
98 #pragma mark HTML Fragments
99
100 - (void)writeHTMLString:(NSString *)html;
101 {
102 [self writeString:html];
103 }
104
105 - (void)writeHTMLFormat:(NSString *)format , ...
106 {
107 va_list argList;
108 va_start(argList, format);
109 NSString *aString = [[[NSString alloc] initWithFormat:format arguments:argList] autorelease];
110 va_end(argList);
111
112 [self writeHTMLString:aString];
113 }
114
115 #pragma mark General
116
117 - (void)startElement:(NSString *)tagName className:(NSString *)className;
118 {
119 [self startElement:tagName idName:nil className:className];
120 }
121
122 - (void)startElement:(NSString *)tagName idName:(NSString *)idName className:(NSString *)className;
123 {
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
124 if (idName) [self pushElementAttribute:@"id" value:idName];
125 if (className) [self pushElementAttribute:@"class" value:className];
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
126
127 [self startElement:tagName];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
128 }
129
130 #pragma mark Line Break
131
132 - (void)writeLineBreak;
133 {
134 [self startElement:@"br"];
135 [self endElement];
136 }
137
138 #pragma mark Higher-level Tag Writing
139
140 - (void)startAnchorElementWithHref:(NSString *)href title:(NSString *)titleString target:(NSString *)targetString rel:(NSString *)relString;
141 {
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
142 if (href) [self pushElementAttribute:@"href" value:href];
143 if (targetString) [self pushElementAttribute:@"target" value:targetString];
144 if (titleString) [self pushElementAttribute:@"title" value:titleString];
145 if (relString) [self pushElementAttribute:@"rel" value:relString];
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
146
147 [self startElement:@"a"];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
148 }
149
ac684bc @mikeabdullah Don't need to pass id and class directly when writing an image.
mikeabdullah authored
150 - (void)writeImageWithSrc:(NSString *)src
151 alt:(NSString *)alt
152 width:(NSString *)width
153 height:(NSString *)height;
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
154 {
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
155 [self pushElementAttribute:@"src" value:src];
156 [self pushElementAttribute:@"alt" value:alt];
157 if (width) [self pushElementAttribute:@"width" value:width];
158 if (height) [self pushElementAttribute:@"height" value:height];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
159
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
160 [self startElement:@"img"];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
161 [self endElement];
162 }
163
164 // TODO: disable indentation & newlines when we are in an anchor tag, somehow.
165
166 #pragma mark Link
167
168 - (void)writeLinkWithHref:(NSString *)href
169 type:(NSString *)type
170 rel:(NSString *)rel
171 title:(NSString *)title
172 media:(NSString *)media;
173 {
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
174 if (rel) [self pushElementAttribute:@"rel" value:rel];
175 if (type) [self pushElementAttribute:@"type" value:type];
176 [self pushElementAttribute:@"href" value:href];
177 if (title) [self pushElementAttribute:@"title" value:title];
178 if (media) [self pushElementAttribute:@"media" value:media];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
179
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
180 [self startElement:@"link"];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
181 [self endElement];
182 }
183
184 - (void)writeLinkToStylesheet:(NSString *)href
185 title:(NSString *)title
186 media:(NSString *)media;
187 {
188 [self writeLinkWithHref:href type:@"text/css" rel:@"stylesheet" title:title media:media];
189 }
190
60c7e89 @mikeabdullah Simpler method for pulling in an external script.
mikeabdullah authored
191 #pragma mark Scripts
192
37c85e9 @mikeabdullah -writeJavascriptWithSrc: actually makes more sense.
mikeabdullah authored
193 - (void)writeJavascriptWithSrc:(NSString *)src;
60c7e89 @mikeabdullah Simpler method for pulling in an external script.
mikeabdullah authored
194 {
27cf398 @mikeabdullah Assertion.
mikeabdullah authored
195 NSParameterAssert(src);
196
1ca811e @mikeabdullah Split out -startJavascriptElementWithSrc:
mikeabdullah authored
197 [self startJavascriptElementWithSrc:src];
60c7e89 @mikeabdullah Simpler method for pulling in an external script.
mikeabdullah authored
198 [self endElement];
199 }
de567f7 added method to output JavaScript (via Dan)
Terrence Talbot authored
200
4d9ca24 @mikeabdullah -writeJavascript:useCDATA: convenience method.
mikeabdullah authored
201 - (void)writeJavascript:(NSString *)script useCDATA:(BOOL)useCDATA;
202 {
1ca811e @mikeabdullah Split out -startJavascriptElementWithSrc:
mikeabdullah authored
203 [self startJavascriptElementWithSrc:nil];
4d9ca24 @mikeabdullah -writeJavascript:useCDATA: convenience method.
mikeabdullah authored
204
205 if (useCDATA) [self startJavascriptCDATA];
206 [self writeString:script];
207 if (useCDATA) [self endJavascriptCDATA];
208
209 [self endElement];
210 }
211
1ca811e @mikeabdullah Split out -startJavascriptElementWithSrc:
mikeabdullah authored
212 - (void)startJavascriptElementWithSrc:(NSString *)src; // src may be nil
213 {
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
214 [self pushElementAttribute:@"type" value:@"text/javascript"]; // in theory, HTML5 pages could omit this
215 if (src) [self pushElementAttribute:@"src" value:src];
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
216
217 [self startElement:@"script"];
8a9783e @mikeabdullah Embedded scripts should start on a newline.
mikeabdullah authored
218
219 // Embedded scripts should start on their own line for clarity
2d68556 @mikeabdullah Embedded scripts should also write their end tag on a new line.
mikeabdullah authored
220 if (!src)
221 {
222 [self writeString:@"\n"];
223 [self stopWritingInline];
224 }
1ca811e @mikeabdullah Split out -startJavascriptElementWithSrc:
mikeabdullah authored
225 }
226
f241869 @mikeabdullah Split out -startJavascriptCDATA and -endJavascriptCDATA.
mikeabdullah authored
227 - (void)startJavascriptCDATA;
228 {
229 [self writeString:@"\n/* "];
230 [self startCDATA];
231 [self writeString:@" */"];
232 }
233
234 - (void)endJavascriptCDATA;
235 {
236 [self writeString:@"\n/* "];
237 [self endCDATA];
238 [self writeString:@" */\n"];
239 }
240
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
241 #pragma mark Style
242
6dd07ee @mikeabdullah Convenience method for writing inline CSS.
mikeabdullah authored
243 - (void)writeStyleElementWithCSSString:(NSString *)css;
244 {
245 [self startStyleElementWithType:@"text/css"];
246 [self writeString:css]; // browsers don't expect styles to be XML escaped
247 [self endElement];
248 }
249
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
250 - (void)startStyleElementWithType:(NSString *)type;
251 {
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
252 if (type) [self pushElementAttribute:@"type" value:type];
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
253 [self startElement:@"style"];
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
254 }
255
256 #pragma mark Elements Stack
257
258 - (BOOL)topElementIsList;
259 {
260 NSString *tagName = [self topElement];
261 BOOL result = [tagName isEqualToString:@"UL"] || [tagName isEqualToString:@"OL"];
262 return result;
263 }
264
c35c721 @mikeabdullah Only a handful of HTML elements are allowed to be empty.
mikeabdullah authored
265 #pragma mark (X)HTML
266
267 - (BOOL)elementCanBeEmpty:(NSString *)tagName;
268 {
269 if ([tagName caseInsensitiveCompare:@"BR"] == NSOrderedSame ||
270 [tagName caseInsensitiveCompare:@"IMG"] == NSOrderedSame ||
271 [tagName caseInsensitiveCompare:@"HR"] == NSOrderedSame ||
272 [tagName caseInsensitiveCompare:@"META"] == NSOrderedSame ||
273 [tagName caseInsensitiveCompare:@"LINK"] == NSOrderedSame ||
274 [tagName caseInsensitiveCompare:@"INPUT"] == NSOrderedSame ||
275 [tagName caseInsensitiveCompare:@"BASE"] == NSOrderedSame ||
276 [tagName caseInsensitiveCompare:@"BASEFONT"] == NSOrderedSame ||
277 [tagName caseInsensitiveCompare:@"PARAM"] == NSOrderedSame ||
278 [tagName caseInsensitiveCompare:@"AREA"] == NSOrderedSame) return YES;
279
280 return NO;
281 }
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
282
283 - (BOOL)canWriteElementInline:(NSString *)tagName;
284 {
285 switch ([tagName length])
286 {
287 case 1:
288 if ([tagName caseInsensitiveCompare:@"A"] == NSOrderedSame ||
289 [tagName caseInsensitiveCompare:@"B"] == NSOrderedSame ||
290 [tagName caseInsensitiveCompare:@"I"] == NSOrderedSame) return YES;
291 break;
292
293 case 2:
294 if ([tagName caseInsensitiveCompare:@"BR"] == NSOrderedSame ||
295 [tagName caseInsensitiveCompare:@"EM"] == NSOrderedSame) return YES;
296 break;
297
298 case 3:
299 if ([tagName caseInsensitiveCompare:@"IMG"] == NSOrderedSame ||
300 [tagName caseInsensitiveCompare:@"SUP"] == NSOrderedSame ||
301 [tagName caseInsensitiveCompare:@"SUB"] == NSOrderedSame ||
302 [tagName caseInsensitiveCompare:@"BIG"] == NSOrderedSame) return YES;
303 break;
304
305 case 4:
306 if ([tagName caseInsensitiveCompare:@"SPAN"] == NSOrderedSame ||
307 [tagName caseInsensitiveCompare:@"FONT"] == NSOrderedSame) return YES;
308 break;
309
310 case 5:
311 if ([tagName caseInsensitiveCompare:@"SMALL"] == NSOrderedSame) return YES;
312 break;
313
314 case 6:
315 if ([tagName caseInsensitiveCompare:@"STRONG"] == NSOrderedSame) return YES;
316 break;
317 }
318
319 return [super canWriteElementInline:tagName];
320 }
321
322 #pragma mark Element Primitives
323
c8bc317 @mikeabdullah Can now ditch -openTag:writeInline:
mikeabdullah authored
324 - (void)startElement:(NSString *)elementName writeInline:(BOOL)writeInline; // for more control
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
325 {
326 // Add in any pre-written classes
6bc0b29 @mikeabdullah Switch to 'push' terminology for element attributes.
mikeabdullah authored
327 NSString *class = [self elementClassName];
ecfabe0 @mikeabdullah Expose -className.
mikeabdullah authored
328 if (class)
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
329 {
330 [_classNames removeAllObjects];
e96c6c8 @mikeabdullah Calling [htmlWriter pushElementAttribute:@"class" value:@"foo"] automati...
mikeabdullah authored
331 [super pushElementAttribute:@"class" value:class];
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
332 }
9beb73b @mikeabdullah Take a bunch of primitive element APIs private. Just need to use -addAtt...
mikeabdullah authored
333
c8bc317 @mikeabdullah Can now ditch -openTag:writeInline:
mikeabdullah authored
334 [super startElement:elementName writeInline:writeInline];
75c4ca6 @mikeabdullah Experimental support for building up CSS class names before writing an e...
mikeabdullah authored
335 }
336
994c91e @mikeabdullah And the real star of our show arrives: KSHTMLWriter!
mikeabdullah authored
337 - (void)closeEmptyElementTag; // /> OR > depending on -isXHTML
338 {
339 if ([self isXHTML])
340 {
341 [super closeEmptyElementTag];
342 }
343 else
344 {
345 [self writeString:@">"];
346 }
347 }
348
349 @end
Something went wrong with that request. Please try again.