1
1
#import " SSYWeblocGuy.h"
2
2
3
+ /* Prior to 2016-08-10, this class did not have a proper state machine for
4
+ parsing the XML expected in a .webloc file. I just took the last string that
5
+ was parsed. This worked because there are only two strings in the file:
6
+ <key>URL</key>
7
+ <string>http://www.example.com</string>
8
+ and the second one was the one I wanted. And so I marked a TODO to fix
9
+ this someday.
10
+
11
+ Now, it is fixed, but I'm no sure if it is better or worse. The goal is to
12
+ future-proof in case Apple changes the .webloc format. But after looking at
13
+ this state machine, I'm not sure it's really any more future proof. It is
14
+ definitely less cheesy. Oh, well, the old code is in git if I want to go back.
15
+ */
16
+
17
+ typedef enum {
18
+ SSYWeblocParserStateZero,
19
+ SSYWeblocParserStateInPlist,
20
+ SSYWeblocParserStateInDict,
21
+ SSYWeblocParserStateParsingAKey,
22
+ SSYWeblocParserStateParsingUrlString,
23
+ SSYWeblocParserStateGotAllWeNeed
24
+ } SSYWeblocParserState ;
25
+
26
+
3
27
@interface SSYWeblocGuy ()
4
28
5
- @property (retain ) NSMutableString * xmlString ;
29
+ @property (retain ) NSMutableString * stringBeingParsed ;
30
+ @property (assign ) SSYWeblocParserState parserState ;
6
31
7
32
@end
8
33
9
34
10
35
@implementation SSYWeblocGuy
11
36
12
- @synthesize xmlString = m_xmlString ;
13
-
14
37
- (void )dealloc {
15
- [m_xmlString release ] ;
38
+ [_stringBeingParsed release ] ;
16
39
17
40
[super dealloc ] ;
18
41
}
@@ -22,29 +45,41 @@ - (void) parser:(NSXMLParser*)parser
22
45
namespaceURI : (NSString *)namespaceURI
23
46
qualifiedName : (NSString *)qualifiedName
24
47
attributes : (NSDictionary *)attributeDict {
25
- if ([elementName isEqualToString: @" string" ]) {
26
- // The contents are collected in parser:foundCharacters:.
27
- m_accumulatingUrl = YES ;
28
- // The mutable string needs to be reset to empty.
29
- [[self xmlString ] setString: @" " ] ;
30
- }
48
+ if ((_parserState == SSYWeblocParserStateZero) && [elementName isEqualToString: @" plist" ]) {
49
+ _parserState = SSYWeblocParserStateInPlist ;
50
+ }
51
+ else if ((_parserState == SSYWeblocParserStateInPlist) && [elementName isEqualToString: @" dict" ]) {
52
+ _parserState = SSYWeblocParserStateInDict ;
53
+ }
54
+ else if ((_parserState == SSYWeblocParserStateInDict) && [elementName isEqualToString: @" key" ]) {
55
+ _parserState = SSYWeblocParserStateParsingAKey ;
56
+ }
57
+ else if ((_parserState == SSYWeblocParserStateParsingUrlString) && [elementName isEqualToString: @" string" ]) {
58
+ if ([self .stringBeingParsed isEqualToString: @" URL" ]) {
59
+ _parserState = SSYWeblocParserStateParsingUrlString ;
60
+ [self .stringBeingParsed setString: @" " ] ;
61
+ }
62
+ }
31
63
}
32
64
33
65
- (void )parser : (NSXMLParser *)parser
34
66
didEndElement : (NSString *)elementName
35
67
namespaceURI : (NSString *)namespaceURI
36
68
qualifiedName : (NSString *)qName {
37
- m_accumulatingUrl = NO ;
69
+ if ((_parserState == SSYWeblocParserStateParsingAKey) && [elementName isEqualToString: @" key" ]) {
70
+ _parserState = SSYWeblocParserStateParsingUrlString ;
71
+ }
72
+ if ((_parserState == SSYWeblocParserStateParsingUrlString) && [elementName isEqualToString: @" string" ]) {
73
+ _parserState = SSYWeblocParserStateGotAllWeNeed ;
74
+ }
38
75
}
39
76
40
77
- (void ) parser : (NSXMLParser *)parser
41
78
foundCharacters : (NSString *)string {
42
-
43
- if (m_accumulatingUrl) {
44
- // If the current element is one whose content we care about, append 'string'
45
- // to the property that holds the content of the current element.
46
- //
47
- [[self xmlString ] appendString: string] ;
79
+ if ((_parserState ==SSYWeblocParserStateParsingAKey) || (_parserState == SSYWeblocParserStateParsingUrlString)) {
80
+ /* I have not investigated why this method gets invoked with
81
+ string = @"\n" in between the real strings. I just filter them out… */
82
+ [[self stringBeingParsed ] appendString: [string stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet ]]] ;
48
83
}
49
84
}
50
85
@@ -53,50 +88,73 @@ - (void) parser:(NSXMLParser*)parser
53
88
NSLog (@" Found error in XML: %@ " , error) ;
54
89
}
55
90
56
- - (NSArray *)weblocFilenamesAndUrlsInPaths : (NSArray *)paths {
57
- NSMutableArray * filenamesAndURLs = [NSMutableArray array ] ;
58
-
59
- for (NSString * path in paths) {
60
- NSString * url = nil ;
61
-
91
+ - (NSDictionary *)filenameAndUrlFromWeblocFileAtPath : (NSString *)path {
92
+ NSString * url = nil ;
93
+ if ([path.pathExtension isEqualToString: @" webloc" ]) {
62
94
NSData * data = [NSData dataWithContentsOfFile: path] ;
95
+
96
+ /* The .webloc files produced by Safari 9.0 (macOS 10.11) or later
97
+ are .plist files. Look for that first. */
63
98
NSDictionary * dic = [NSPropertyListSerialization propertyListWithData: data
64
99
options: 0
65
100
format: NULL
66
101
error: NULL ] ;
67
102
url = [dic objectForKey: @" URL" ] ;
103
+
68
104
if (!url) {
69
- if (data) {
105
+ /* We are parsing a .webloc file produced by Safari 5 - 8, which
106
+ uses XML format. */
107
+ if (data.length > 0 ) {
70
108
NSXMLParser * parser = [[NSXMLParser alloc ] initWithData: data] ;
71
109
[parser setDelegate: self ] ;
72
- NSMutableString * xmlString = [[ NSMutableString alloc ] init ] ;
73
- [ self setXmlString: xmlString ] ;
74
- [xmlString release ] ;
75
-
110
+ self. parserState = SSYWeblocParserStateZero ;
111
+ NSMutableString * stringBeingParsed = [ NSMutableString new ] ;
112
+ self. stringBeingParsed = stringBeingParsed ;
113
+ [stringBeingParsed release ] ;
76
114
[parser parse ] ;
77
- // Note that -parse is synchronous and will not return until the parsing
78
- // is done or aborted.
115
+ /* Note: -parse is synchronous and will not return until the parsing
116
+ is done or aborted. */
79
117
[parser release ] ;
80
118
81
- url = [self xmlString ] ;
82
-
83
- // Not really necessary, but for resource usage efficiency we
84
- // release xmlString here instead of in -dealloc…
85
- [self setXmlString: nil ] ;
119
+ url = [self .stringBeingParsed copy ] ;
120
+ [url autorelease ] ;
121
+ self.stringBeingParsed = nil ;
86
122
}
87
123
}
88
-
89
- if (url) {
90
- NSString * filename = [[path lastPathComponent ] stringByDeletingPathExtension ] ;
91
-
92
- NSDictionary * filenameAndURL = [NSDictionary dictionaryWithObjectsAndKeys:
93
- filename, @" filename" ,
94
- url, @" url" ,
95
- nil ] ;
96
-
97
- [filenamesAndURLs addObject: filenameAndURL] ;
124
+ }
125
+
126
+ NSDictionary * answer ;
127
+ if (url.length > 0 ) {
128
+ NSString * filename = [[path lastPathComponent ] stringByDeletingPathExtension ] ;
129
+ answer = [NSDictionary dictionaryWithObjectsAndKeys:
130
+ [filename stringByDeletingPathExtension ], @" filename" ,
131
+ url, @" url" ,
132
+ nil ] ;
133
+ }
134
+ else {
135
+ answer = nil ;
136
+ }
137
+
138
+ return answer ;
139
+ }
140
+
141
+ + (NSDictionary *)filenameAndUrlFromWeblocFileAtPath : (NSString *)path {
142
+ SSYWeblocGuy* instance = [[SSYWeblocGuy alloc ] init ] ;
143
+ NSDictionary * answer = [instance filenameAndUrlFromWeblocFileAtPath: path] ;
144
+ [instance release ] ;
145
+
146
+ return answer ;
147
+ }
148
+
149
+ - (NSArray *)weblocFilenamesAndUrlsInPaths : (NSArray *)paths {
150
+ NSMutableArray * filenamesAndURLs = [NSMutableArray array ] ;
151
+
152
+ for (NSString * path in paths) {
153
+ NSDictionary * filenameAndUrl = [self filenameAndUrlFromWeblocFileAtPath: path] ;
154
+ if (filenameAndUrl) {
155
+ [filenamesAndURLs addObject: filenameAndUrl] ;
98
156
}
99
- }
157
+ }
100
158
101
159
if ([filenamesAndURLs count ])
102
160
return [[[NSArray alloc ] initWithArray: filenamesAndURLs] autorelease ] ;
0 commit comments