source: trunk/PrefsPane/src/Xml.m @ 1046

Revision 965, 12.1 KB checked in by speck, 2 years ago (diff)

Optimize [Xml makeUniqueIdWithRoot] as using XPath for checking for duplicate id makes loading of filters with many rules quite slow.

Line 
1/* Copyright (C) 2008-2009 Peter Speck
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#import "Xml.h"
18
19@implementation Xml
20
21+ (BOOL)isSimpleName:(NSString*)name startIndex:(int)startIndex
22{
23    NSInteger len = [name length];
24    for (int i = startIndex; i < len; i++) {
25        unichar ch = [name characterAtIndex:i];
26        if (ch == '-' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z'))
27            continue;
28        return NO;
29    }
30    return len > 0;
31}
32
33+ (BOOL)isSimpleName:(NSString*)name
34{
35    return [Xml isSimpleName:name startIndex:0];
36}
37
38#pragma mark -------------- Create elements
39
40+ (NSXMLElement*)createElementWithName:(NSString*)name
41{
42    return (NSXMLElement*)[NSXMLNode elementWithName:name];
43}
44
45#pragma mark -------------- Singletons
46
47+ (NSXMLElement*)singletonElementForXPath:(NSString*)xpath
48                                forParent:(NSXMLElement*)parentE
49{
50    return [self singletonElementForXPath:xpath forParent:parentE asFirstChild:NO];
51}
52
53+ (NSXMLElement*)singletonElementForXPath:(NSString*)xpath
54                                forParent:(NSXMLElement*)parentE
55                             asFirstChild:(BOOL)asFirstChild
56{
57    NSArray* pathItems = [xpath componentsSeparatedByString:@"/"];
58    NSUInteger compCount = [pathItems count];
59    NSXMLElement* e = parentE;
60    for (NSUInteger compIdx = 0; true; compIdx++) {
61        NSString* s = [pathItems objectAtIndex:compIdx];
62        if (![Xml isSimpleName:s]) {
63            [NSException raise:@"Invalid element name in singleton xpath"
64                        format:@"xpath = '%@'", xpath];
65        }
66        BOOL leaf = (compIdx == compCount - 1);
67        NSArray* childs = [e elementsForName:s];
68        NSInteger numChilds = [childs count];
69        if (leaf && numChilds > 1) {
70            for (int x = 1; x < numChilds; x++)
71                [[childs objectAtIndex:compIdx] detach];
72        }
73        if (numChilds) {
74            e = [childs objectAtIndex:0];
75        } else {
76            NSXMLElement* newE = [Xml createElementWithName:s];
77            if (leaf && asFirstChild)
78                [e insertChild:newE atIndex:0];
79            else
80                [e addChild:newE];
81            e = newE;
82        }
83        if (leaf)
84            return e;
85    }
86    [NSException raise:@"shouldn't fell through" format:@"xpath = '%@'", xpath];
87}
88
89+ (NSXMLElement*)singletonDateElement:(NSDate*)date
90                             forXPath:(NSString*)xpath
91                            fromParent:(NSXMLElement*)parentE
92                       updateExisting:(BOOL)updateExisting
93{
94    NSXMLElement* e = [Xml singletonElementForXPath:xpath forParent:parentE asFirstChild:YES];
95    if (!updateExisting && [Xml getAttribute:e withName:@"msecs"])
96        return e;
97    NSString* msecs = [NSString stringWithFormat:@"%.0f", [date timeIntervalSince1970] * 1000];
98    [Xml setAttribute:msecs withName:@"msecs" inElement:e];
99    [Xml setAttribute:[date description] withName:@"iso" inElement:e];
100    return e;
101}
102
103#pragma mark -------------- Attributes
104
105+ (NSString*)getAttribute:(NSXMLElement*)e withName:(NSString*)name
106{
107    return [[e attributeForName:name] stringValue];
108}
109
110+ (int)getBoolAttribute:(NSXMLElement*)e withName:(NSString*)name
111{
112    return [Xml getIntAttribute:e withName:name] ? YES : NO;
113}
114
115+ (int)getIntAttribute:(NSXMLElement*)e withName:(NSString*)name
116{
117    return [[Xml getAttribute:e withName:name] intValue];
118}
119
120+ (void)setAttribute:(NSString*)value withName:(NSString*)name inElement:(NSXMLElement*)e
121{
122    NSXMLNode* attr = [e attributeForName:name];
123    if ([value length]) {
124        if (![[attr stringValue] isEqual:value]) {
125            [attr detach];
126            [e addAttribute:[NSXMLNode attributeWithName:name stringValue:value]];
127        }
128    } else {
129        [attr detach];
130    }
131}
132
133+ (void)setBool:(BOOL)value withName:(NSString*)name inElement:(NSXMLElement*)e
134{
135    [Xml setAttribute:(value ? @"1" : NULL) withName:name inElement:e];
136}
137
138+ (void)setInt:(int)value withName:(NSString*)name inElement:(NSXMLElement*)e
139{
140    [Xml setAttribute:[NSString stringWithFormat:@"%d", value] withName:name inElement:e];
141}
142
143+ (void)reorderChilds:(NSArray*)childs
144{
145    if ([childs count] <= 1)
146        return;
147    for (NSUInteger i = 0; i < [childs count]; i++) {
148        NSXMLElement *childE = [childs objectAtIndex:i];
149        if (!childE) {
150            NSLog(@"NULL element in array in reorder childs");
151            continue;
152        }
153        NSXMLElement *parentE = (NSXMLElement*)[childE parent];
154        if (!parentE) {
155            NSLog(@"Element without parent in reorder childs.");
156            continue;
157        }
158        [childE retain];
159        [childE detach];
160        [parentE addChild:childE];
161        [childE release];
162    }
163}
164
165#pragma mark -------------------------------- XPath
166
167+ (NSArray *)nodesForXPath:(NSString *)xpath withRoot:(NSXMLElement*)rootE
168{
169    NSAssert(rootE, @"rootE");
170    NSError *error = NULL;
171    NSArray* a = [rootE nodesForXPath:xpath error:&error];
172    if (error)
173        NSLog(@"Xml.nodesForXPath failed with '%@' for xpath '%@' at location '%@'",
174              error, xpath, [rootE XPath]);
175    if (!a)
176        a = [NSArray array];
177    return a;
178}
179
180+ (NSXMLNode *)nodeForXPath:(NSString *)xpath withRoot:(NSXMLElement*)rootE
181{
182    NSArray* a = [self nodesForXPath:xpath withRoot:rootE];
183    return [a count] > 0 ? [a objectAtIndex:0] : NULL;
184}
185
186+ (void)remove:(NSString*)xpath withRoot:(NSXMLElement*)rootE
187{
188    NSArray* a = [self nodesForXPath:xpath withRoot:rootE];
189    for (NSUInteger i = 0; i < [a count]; i++) {
190        NSXMLNode* e = [a objectAtIndex:i];
191        [e detach];
192    }
193}
194
195+ (void)zapChildsAndAttributes:(NSXMLElement*)e
196{
197    for (NSXMLNode* a in [e attributes]) {
198        if (![[a name] isEqual:@"rule-id"]) {
199            //NSLog(@"Zaps attr: %@", a);
200            [a detach];
201        }
202    }
203    [e setChildren:[NSArray array]];
204}
205
206#pragma mark -------------------------------- simple get/set
207
208+ (NSString*)stringForXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
209{
210    NSArray* a = [Xml nodesForXPath:xpath withRoot:rootE];
211    return [a count] ? [[a objectAtIndex:0] stringValue] : NULL;
212}
213
214+ (void)setText:(NSString*)value
215       forXPath:(NSString*)xpath
216       withRoot:(NSXMLElement*)rootE
217     usingCDATA:(BOOL)usingCDATA
218{
219    NSArray* pathItems = [xpath componentsSeparatedByString:@"/"];
220    NSUInteger num = [pathItems count];
221    NSXMLElement* e = rootE;
222    for (NSUInteger i = 0; i < num; i++) {
223        NSString* s = [pathItems objectAtIndex:i];
224        if (![s length]) {
225            [NSException raise:@"Invalid setString xpath"
226                        format:@"xpath = '%@'", xpath];
227        }
228        if ([s isEqual:@"."])
229            continue;
230        if ([s hasPrefix:@"@"]) {
231            if (i + 1 < num) {
232                [NSException raise:@"Invalid setString xpath"
233                            format:@"xpath = '%@'", xpath];
234            }
235            if (![Xml isSimpleName:s startIndex:1]) {
236                [NSException raise:@"Invalid attribute name in setString xpath"
237                            format:@"xpath = '%@'", xpath];
238            }
239            NSString* attrName = [s substringFromIndex:1];
240            [Xml setAttribute:value withName:attrName inElement:e];
241            return;
242        }
243        if (![Xml isSimpleName:s]) {
244            [NSException raise:@"Invalid element name in setString xpath"
245                        format:@"xpath = '%@'", xpath];
246        }
247        NSArray* childs = [e elementsForName:s];
248        if ([childs count]) {
249            e = [childs objectAtIndex:0];
250        } else {
251            NSXMLElement* newE = [Xml createElementWithName:s];
252            [e addChild:newE];
253            e = newE;
254        }
255    }
256    for (NSXMLElement* zapE in [e children])
257        [zapE detach];
258    if (![value length])
259        return;
260    if (usingCDATA) {
261        //NSLog(@"SetCDATA in %@: {{{%@}}}", [e name], value);
262        NSXMLNode* cdata = [[NSXMLNode alloc] initWithKind:NSXMLTextKind options:NSXMLNodeIsCDATA];
263        [cdata setStringValue:value];
264        [e addChild:cdata];
265        [cdata release];
266    } else {
267        [e setStringValue:value];
268    }
269}
270
271+ (void)setString:(NSString*)value forXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
272{
273    [self setText:value forXPath:xpath withRoot:rootE usingCDATA:NO];
274}
275
276+ (void)setCData:(NSString*)value forXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
277{
278    [self setText:value forXPath:xpath withRoot:rootE usingCDATA:YES];
279}
280
281+ (double)doubleForXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
282{
283    return [[Xml stringForXPath:xpath withRoot:rootE] doubleValue];
284}
285
286+ (void)setDouble:(double)value forXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
287{
288    [Xml setString:[NSString stringWithFormat:@"%f", value] forXPath:xpath withRoot:rootE];
289}
290
291+ (int)intForXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
292{
293    return [[Xml stringForXPath:xpath withRoot:rootE] intValue];
294}
295
296+ (void)setInt:(int)value forXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
297{
298    [Xml setString:[NSString stringWithFormat:@"%d", value] forXPath:xpath withRoot:rootE];
299}
300
301+ (BOOL)boolForXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
302{
303    NSString* s = [Xml stringForXPath:xpath withRoot:rootE];
304    return s && [s length] && ![s isEqual:@"false"] && ![s isEqual:@"0"];
305}
306
307+ (void)setBool:(BOOL)value forXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
308{
309    [Xml setString:(value ? @"1" : NULL) forXPath:xpath withRoot:rootE];
310}
311
312+ (NSString*)dateFromMillisecsXPath:(NSString*)xpath withRoot:(NSXMLElement*)rootE
313{
314    double msecs = [Xml doubleForXPath:xpath withRoot:rootE];
315    if (!msecs)
316        return NULL;
317    NSDate* date = [NSDate dateWithTimeIntervalSince1970:msecs / 1000.0];
318    //
319    NSDateFormatter* fmt = [[[NSDateFormatter alloc] init] autorelease];
320    [fmt setDateStyle:NSDateFormatterMediumStyle];
321    [fmt setTimeStyle:NSDateFormatterShortStyle];
322    return [fmt stringFromDate:date];
323}
324
325#pragma mark -------------------------------- regexp
326
327+ (BOOL)regex:(NSString*)regex matchesString:(NSString*)text
328{
329    NSString *fmt = [NSString stringWithFormat:@"SELF MATCHES '%@'", regex];
330    NSPredicate *predicate = [NSPredicate predicateWithFormat:fmt];
331    return [predicate evaluateWithObject:text];
332}
333
334+ (NSString*)getHttpUrlSyntaxError:(NSString*)url
335{
336    if (![url length])
337        return @"No URL was specified";
338    if (![url hasPrefix:@"http://"] && ![url hasPrefix:@"https://"])
339        return @"URL must start with 'http://' or 'https://'";
340    if (![NSURL URLWithString:url])
341        return @"Invalid URL (NSURL)";
342    if (![Xml regex:@"^https?://[^:/]+(:[0-9]+)?(/[^[:space:]]*)?[:space:]*$" matchesString:url])
343        return @"Invalid URL syntax";
344    return NULL;
345}
346
347#pragma mark -------------------------------- unqiue id
348
349// Faster than using XPath. Was bottleneck in loading new filters.
350+ (BOOL)hasAttribute:(NSString*)attrName inSubtree:(NSXMLElement*)parentE withValue:(NSString*)value
351{
352    NSString* s = [[parentE attributeForName:attrName] stringValue];
353    if (s && [s isEqualToString:value])
354        return YES;
355    for (NSXMLNode* childN in [parentE children]) {
356        if ([childN kind] == NSXMLElementKind
357            && [Xml hasAttribute:attrName inSubtree:(NSXMLElement*)childN withValue:value]) {
358            return YES;
359        }
360    }
361    return NO;
362}
363
364+ (int)makeUniqueIdWithRoot:(NSXMLElement*)rootE withAttributeName:(NSString*)attrName
365{
366    while (true) {
367        int r = (int)arc4random() & 0x3FFFFFFF;
368        if (r < 1234567)
369            continue;
370        NSString* value = [NSString stringWithFormat:@"%d", r];
371        if (![Xml hasAttribute:attrName inSubtree:rootE withValue:value])
372            return r;
373    }
374}
375
376+ (int)makeUniqueIdWithRoot:(NSXMLElement*)rootE
377{
378    return [Xml makeUniqueIdWithRoot:rootE withAttributeName:@"id"];
379}
380
381#pragma mark -------------------------------- print-to-string
382
383+ (NSData*)documentToData:(NSXMLDocument*)doc
384{
385    int options = NSXMLNodePrettyPrint|NSXMLNodePreserveCDATA;
386    NSMutableData* data = [NSMutableData dataWithData:[doc XMLDataWithOptions:options]];
387    [data appendBytes:"\n" length:1]; // XMLDataWithOptions doesn't end with newline.
388    return data;
389}
390
391+ (NSString*)documentToString:(NSXMLDocument*)doc;
392{
393    NSData* data = [Xml documentToData:doc];
394    return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
395}
396
397// Cocoa doesn't support this for anything but full documents,
398// and using [NSString stringWithFormat:@"%@", rootE] is dubious long-term.
399+ (NSData*)subtreeToData:(NSXMLElement*)parentE;
400{
401    parentE = [parentE copy];
402    NSXMLDocument* doc = [[NSXMLDocument alloc] initWithRootElement:parentE];
403    NSData* data = [Xml documentToData:doc];
404    [doc release];
405    [parentE release];
406    return data;
407}
408
409+ (NSString*)subtreeToString:(NSXMLElement*)parentE
410{
411    NSData* data = [Xml subtreeToData:parentE];
412    return [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
413}
414
415@end
Note: See TracBrowser for help on using the repository browser.