-
Notifications
You must be signed in to change notification settings - Fork 12
/
KSXMLWriter.h
306 lines (225 loc) · 10 KB
/
KSXMLWriter.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//
// KSXMLWriter.h
//
// Created by Mike Abdullah
// Copyright © 2010 Karelia Software
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <KSWriter/KSWriter.h>
#import "KSXMLAttributes.h"
NS_ASSUME_NONNULL_BEGIN
@interface KSXMLWriter : NSObject
#pragma mark Creating an XML Writer
// .encoding is taken from the writer. If output writer is nil, defaults to UTF-8
- (id)initWithOutputWriter:(nullable KSWriter *)output NS_DESIGNATED_INITIALIZER;
#pragma mark Document
/**
The document's type, which we hang onto so clients can get some information about the XML being
written if they need to. Avoid changing this mid-writing as would likely confuse clients.
nil by default, but subclasses might override that. For example, KSHTMLWriter does, to default to
the HTML 5 doctype.
*/
@property(nonatomic, copy, nullable) NSString *doctype;
/**
Writes a doctype declaration according to the receiver's \c docType, which must be non-nil. Example:
<!DOCTYPE %docType%>
*/
- (void)writeDoctypeDeclaration;
#pragma mark Elements
/**
The primitive API for beginning an element. Each call must be matched with a later call to \c endElement
once you've written the contents of the element.
An element's contents may well include other elements — this is XML after all! The writer maintains
a stack of the open elements to track it all.
It's often more convenient to use one of the \c writeElement:… methods. There's no need to call
\c endElement yourself, and your code is nicely indented in a similar fashion to the markup
produced :-)
*/
- (void)startElement:(NSString *)elementName;
/**
Ends the current element. Uses the element stack to know the tag to write.
*/
- (void)endElement;
/**
Convenience that starts \c elementName, executes the \c content block, then ends the element.
*/
- (void)writeElement:(NSString *)elementName content:(nullable void (^)(void))content;
/**
Convenience for writing <tag>text</tag>
*/
- (void)writeElement:(NSString *)elementName text:(NSString *)text;
#pragma mark Attributes
/**
Appends an attribute to the current element. Importantly this ONLY works in the time between an
element being started, and the first bit of content being written. For example, this is fine:
[writer startElement:@"foo"];
[writer addAttribute:@"bar" value:@"true"];
[writer writeCharacters:@"text"];
and so is this:
[writer writeElement:@"foo" content:^{
[writer addAttribute:@"foo" value:@"bar"]);
[writer writeCharacters:@"text"];
}];
But this will throw an exception since the writer has nowhere to add the attribite *to*:
[writer writeElement:@"foo" content:^{
[writer writeCharacters:@"text"];
[writer addAttribute:@"foo" value:@"bar"]);
}];
*/
- (void)addAttribute:(NSString *)attribute value:(NSString *)value;
/* You can also gain finer-grained control over element attributes. KSXMLWriter maintains a list of attributes that will be applied when you *next* call one of the -startElement: methods. This has several advantages:
* - Attributes are written in exactly the order you specify
* - More efficient than building up a temporary dictionary object
* - Can sneak extra attributes in when using a convenience method (e.g. for HTML)
*
* The stack is cleared for you each time an element starts, to save the trouble of manually managing that.
*/
- (void)pushAttribute:(NSString *)attribute value:(id)value;
/**
@result a copy of the current attributes stack
*/
- (KSXMLAttributes *)currentAttributes;
/**
Handy way to find if there's any attributes pushed without the overhead of copying \c currentAttributes
*/
- (BOOL)hasCurrentAttributes;
/**
Like +stringFromCharacters: but for attributes, where quotes need to be escaped
*/
+ (NSString *)stringFromAttributeValue:(NSString *)value;
#pragma mark Pretty Printing
/**
Start a new line for pretty printing purposes, including tabs to match \c indentationLevel.
Normally newlines are automatically written for you (if pretty-printing is enabled). You can call
this if you need an extra one, or are implementing your own extra control over formatting.
KSXMLWriter uses this as the cue to know that the current element spans multiple lines, and
therefore its end tag should be written on a new line too.
*/
- (void)startNewline;
/**
Whether the receiver should create human-friendly output by indenting elements, and placing them on
a newline. The default is \c NO, but \c KSHTMLWriter does the opposite, to make pretty-printing on
by default.
Pretty-printing is implemented such that when starting an element, it is placed onto a new line,
with an increased \c indentationLevel. There are some exceptions:
- When starting a document, if the first bit of content is an element, it doesn't make sense to
place that on a new line because if we did, you'd be left with a weird empty line at the start of
the document.
- You can call \c -resetPrettyPrinting to make use of the mechanism described above so as to force
the writer not to insert a newline for a moment.
- For HTML, some elements want to be written inline anyway for optimum prettiness. E.g. \c EM tags
inside of a paragraph. `shouldPrettyPrintElementInline` is consulted to find out if that is the
case, so as to bypass the newline behaviour.
By waiting until the _start_ of an element, clients are able to do a little customisation with the
_end_ of elements. For example, you can add a comment straight after the end tag, and that won't
get shunted onto a new line.
*/
@property(nonatomic) BOOL prettyPrint;
/**
Resets the system so that the next element to be written is *not* given a new line to itself,
regardless of tag name etc. You don't need to call this under normal circumstances, but it can be
handy for odd occasions where you need to temporarily disable pretty printing.
*/
- (void)resetPrettyPrinting;
/**
When starting an element with \c prettyPrint turned on, this gets called to decide if \c element
should be written inline, or begin on a new line.
The default implementation returns \c NO. \c KSHTMLWriter overrides to know about a variety of
common HTML elements.
*/
+ (BOOL)shouldPrettyPrintElementInline:(NSString *)element;
/**
The number of tabs to indent by whenever `startNewline` is called. You do not normally need to
adjust this property mid-writing as starting/ending elements etc. automatically adjust the level to
match.
*/
@property(nonatomic) NSUInteger indentationLevel;
- (void)increaseIndentationLevel;
/**
Attempting to decrease the indentation level to a negative value will log an error message and go
otherwise ignored.
*/
- (void)decreaseIndentationLevel;
#pragma mark Elements Stack
// XMLWriter maintains a stack of the open elements so it knows how to end them. You probably don't ever care about this, but it can be handy in more advanced use cases
/**
A copy of the current elements stack
*/
@property(nonatomic, readonly) NSArray *openElements;
- (NSUInteger)openElementsCount;
- (BOOL)hasOpenElement:(NSString *)tagName;
- (nullable NSString *)topElement;
- (void)pushElement:(NSString *)element;
- (void)popElement;
#pragma mark Element Primitives
- (void)closeEmptyElementTag;
#pragma mark Output
/**
This is the primitive API through which all output is channeled. The requested \c range of \c string
is sent through to \c outputWriter. Any characters which aren't supported by the receiver's
\c encoding are XML escaped before sending through.
*/
- (void)writeString:(NSString *)string range:(NSRange)range;
/**
Convenience that calls straight through to \c writeString:range: requesting the whole string be
written
*/
- (void)writeString:(NSString *)string;
/**
Automatically taken from the \c outputWriter. Defaults to UTF8 if there is no output
*/
@property(nonatomic, readonly) NSStringEncoding encoding;
/** we support ASCII, UTF8, ISO Latin 1, and Unicode at present
*/
+ (BOOL)isStringEncodingAvailable:(NSStringEncoding)encoding;
@property(nullable, readonly) KSWriter *outputWriter;
@end
@interface KSXMLWriter (CharacterData)
#pragma mark Text
/**
Escapes any XML entities, passing the results through to \c -writeString:
NOT intended for other text-like strings such as element attributes. Use other APIs for that instead.
*/
- (void)writeCharacters:(NSString *)string;
/**
Convenience to perform escaping without instantiating a writer.
*/
+ (NSString *)stringFromCharacters:(NSString *)string;
#pragma mark Comments
/**
Writes a comment tag, escaping the text as needed.
*/
- (void)writeComment:(NSString *)comment;
- (void)openComment;
- (void)closeComment;
#pragma mark CDATA
- (void)startCDATA;
- (void)endCDATA;
- (void)writeCDATAWithContentBlock:(void (^)(void))content;
@end
@interface KSXMLWriter (Validation)
/**
Default implementation returns YES. Subclasses can override to advise that the writing of an
element would result in invalid markup
*/
- (BOOL)validateElement:(NSString *)element;
- (NSString *)validateAttribute:(NSString *)name value:(NSString *)value ofElement:(NSString *)element;
@end
NS_ASSUME_NONNULL_END