--- /dev/null
+/*
+ Copyright (C) 2005 Helge Hess
+
+ This file is part of SOPE.
+
+ SOPE is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with SOPE; see the file COPYING. If not, write to the
+ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA.
+*/
+
+// Note: this does not yet produce valid XML output
+
+#import <Foundation/NSObject.h>
+#include <SaxObjC/SaxObjC.h>
+
+@interface vcf2xml : NSObject
+{
+ id<NSObject,SaxXMLReader> parser;
+ id sax;
+}
+
+- (int)runWithArguments:(NSArray *)_args;
+
+@end
+
+#include "common.h"
+
+@interface MySAXHandler : SaxDefaultHandler
+{
+ id locator;
+ int indent;
+
+ NSString *lastNS;
+}
+
+- (void)indent;
+
+@end
+
+@implementation vcf2xml
+
+- (id)init {
+ if ((self = [super init]) != nil) {
+ self->parser = [[[SaxXMLReaderFactory standardXMLReaderFactory]
+ createXMLReaderForMimeType:@"text/x-vcard"] retain];
+ if (parser == nil) {
+ fprintf(stderr, "Error: could not load a vCard SAX driver bundle!\n");
+ exit(2);
+ }
+ //NSLog(@"Using parser: %@", self->parser);
+
+ self->sax = [[MySAXHandler alloc] init];
+ [parser setContentHandler:self->sax];
+ [parser setErrorHandler:self->sax];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self->sax release];
+ [self->parser release];
+ [super dealloc];
+}
+
+/* process files */
+
+- (void)processFile:(NSString *)_path {
+ [self->parser parseFromSystemId:_path];
+}
+
+/* error handling */
+
+- (NSException *)handleException:(NSException *)_exc onPath:(NSString *)_p {
+ fprintf(stderr, "Error: catched exception on path '%s': %s\n",
+ [_p cString], [[_exc description] cString]);
+ return nil;
+}
+
+/* main entry */
+
+- (int)runWithArguments:(NSArray *)_args {
+ NSEnumerator *args;
+ NSString *arg;
+
+ /* begin processing */
+
+ args = [_args objectEnumerator];
+ [args nextObject]; // skip tool name ...
+
+ while ((arg = [args nextObject]) != nil) {
+ NSAutoreleasePool *pool2;
+
+ if ([arg hasPrefix:@"-"]) { /* consume defaults */
+ [args nextObject];
+ continue;
+ }
+
+ pool2 = [[NSAutoreleasePool alloc] init];
+
+
+ if (![arg isAbsolutePath]) {
+ arg = [[[NSFileManager defaultManager] currentDirectoryPath]
+ stringByAppendingPathComponent:arg];
+ }
+
+ NS_DURING
+ [self->parser parseFromSystemId:arg];
+ NS_HANDLER
+ [[self handleException:localException onPath:arg] raise];
+ NS_ENDHANDLER;
+
+ [pool2 release];
+ }
+ return 0;
+}
+
+@end /* vcf2xml */
+
+
+@implementation MySAXHandler
+
+- (void)dealloc {
+ [self->lastNS release];
+ [self->locator release];
+ [super dealloc];
+}
+
+/* output */
+
+- (void)indent {
+ int i;
+
+ for (i = 0; i < (self->indent * 4); i++)
+ fputc(' ', stdout);
+}
+
+/* documents */
+
+- (void)setDocumentLocator:(id<NSObject,SaxLocator>)_loc {
+ [self->locator autorelease];
+ self->locator = [_loc retain];
+}
+
+- (void)startDocument {
+ //puts("start document ..");
+ //self->indent++;
+}
+- (void)endDocument {
+ //self->indent--;
+ //puts("end document.");
+}
+
+- (void)startPrefixMapping:(NSString *)_prefix uri:(NSString *)_uri {
+ [self indent];
+ //printf("ns-map: %s=%s\n", [_prefix cString], [_uri cString]);
+}
+- (void)endPrefixMapping:(NSString *)_prefix {
+ [self indent];
+ //printf("ns-unmap: %s\n", [_prefix cString]);
+}
+
+- (void)startElement:(NSString *)_localName
+ namespace:(NSString *)_ns
+ rawName:(NSString *)_rawName
+ attributes:(id<SaxAttributes>)_attrs
+{
+ int i, c;
+ [self indent];
+ printf("<%s", [_localName cString]);
+
+ if ([_ns length] > 0) {
+ if ([_ns isEqualToString:self->lastNS])
+ ;
+ else {
+ printf(" xmlns='%s'", [_ns cString]);
+ ASSIGNCOPY(self->lastNS, _ns);
+ }
+ }
+
+ for (i = 0, c = [_attrs count]; i < c; i++) {
+ NSString *type;
+ NSString *ans;
+
+ ans = [_attrs uriAtIndex:i];
+
+ printf(" %s=\"%s\"",
+ [[_attrs nameAtIndex:i] cString],
+ [[_attrs valueAtIndex:i] cString]);
+
+ if (![_ns isEqualToString:ans])
+ printf("(ns=%s)", [ans cString]);
+
+ type = [_attrs typeAtIndex:i];
+ if (![type isEqualToString:@"CDATA"] && (type != nil))
+ printf("[%s]", [type cString]);
+ }
+ puts(">");
+ self->indent++;
+}
+- (void)endElement:(NSString *)_localName
+ namespace:(NSString *)_ns
+ rawName:(NSString *)_rawName
+{
+ self->indent--;
+ [self indent];
+ printf("</%s>\n", [_localName cString]);
+}
+
+- (void)characters:(unichar *)_chars length:(int)_len {
+ NSString *str;
+ id tmp;
+ unsigned i, len;
+
+ if (_len == 0) {
+ [self indent];
+ printf("\"\"\n");
+ return;
+ }
+
+ for (i = 0; i < (unsigned)_len; i++) {
+ if (_chars[i] > 255) {
+ NSLog(@"detected large char: o%04o d%03i h%04X",
+ _chars[i], _chars[i], _chars[i]);
+ }
+ }
+
+ str = [NSString stringWithCharacters:_chars length:_len];
+ len = [str length];
+
+ tmp = [str componentsSeparatedByString:@"\n"];
+ str = [tmp componentsJoinedByString:@"\\n"];
+ tmp = [str componentsSeparatedByString:@"\r"];
+ str = [tmp componentsJoinedByString:@"\\r"];
+
+ [self indent];
+ printf("\"%s\"\n", [str cString]);
+}
+- (void)ignorableWhitespace:(unichar *)_chars length:(int)_len {
+ NSString *data;
+ id tmp;
+
+ data = [NSString stringWithCharacters:_chars length:_len];
+ tmp = [data componentsSeparatedByString:@"\n"];
+ data = [tmp componentsJoinedByString:@"\\n"];
+ tmp = [data componentsSeparatedByString:@"\r"];
+ data = [tmp componentsJoinedByString:@"\\r"];
+
+ [self indent];
+ printf("whitespace: \"%s\"\n", [data cString]);
+}
+
+- (void)processingInstruction:(NSString *)_pi data:(NSString *)_data {
+ [self indent];
+ printf("PI: '%s' '%s'\n", [_pi cString], [_data cString]);
+}
+
+#if 0
+- (xmlEntityPtr)getEntity:(NSString *)_name {
+ NSLog(@"get entity %@", _name);
+ return NULL;
+}
+- (xmlEntityPtr)getParameterEntity:(NSString *)_name {
+ NSLog(@"get para entity %@", _name);
+ return NULL;
+}
+#endif
+
+/* entities */
+
+- (id)resolveEntityWithPublicId:(NSString *)_pubId
+ systemId:(NSString *)_sysId
+{
+ [self indent];
+ printf("shall resolve entity with '%s' '%s'",
+ [_pubId cString], [_sysId cString]);
+ return nil;
+}
+
+/* errors */
+
+- (void)warning:(SaxParseException *)_exception {
+ NSLog(@"warning(%@:%i): %@",
+ [[_exception userInfo] objectForKey:@"publicId"],
+ [[[_exception userInfo] objectForKey:@"line"] intValue],
+ [_exception reason]);
+}
+
+- (void)error:(SaxParseException *)_exception {
+ NSLog(@"error(%@:%i): %@",
+ [[_exception userInfo] objectForKey:@"publicId"],
+ [[[_exception userInfo] objectForKey:@"line"] intValue],
+ [_exception reason]);
+}
+
+- (void)fatalError:(SaxParseException *)_exception {
+ NSLog(@"fatal error(%@:%i): %@",
+ [[_exception userInfo] objectForKey:@"publicId"],
+ [[[_exception userInfo] objectForKey:@"line"] intValue],
+ [_exception reason]);
+ [_exception raise];
+}
+
+@end /* MySAXHandler */
+
+
+
+int main(int argc, char **argv, char **env) {
+ NSAutoreleasePool *pool;
+ vcf2xml *tool;
+ int rc;
+
+ pool = [[NSAutoreleasePool alloc] init];
+#if LIB_FOUNDATION_LIBRARY
+ [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+
+ if ((tool = [[vcf2xml alloc] init])) {
+ NS_DURING
+ rc = [tool runWithArguments:[[NSProcessInfo processInfo] arguments]];
+ NS_HANDLER
+ abort();
+ NS_ENDHANDLER;
+
+ [tool release];
+ }
+ else
+ rc = 1;
+
+ [pool release];
+ return rc;
+}
@interface VSSaxTag : NSObject
{
-@public
- NSString *type;
+@private
+ char type;
NSString *tagName;
+ NSString *group;
+@public
SaxAttributes *attrs;
- NSString *data;
+ unichar *data;
+ unsigned int datalen;
}
-+ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs;
++ (id)beginTag:(NSString *)_tag group:(NSString *)_group
+ attributes:(SaxAttributes *)_attrs;
- (id)initEndTag:(NSString *)_tag;
-- (id)initWithData:(NSString *)_data;
+
+- (id)initWithContentString:(NSString *)_data;
- (NSString *)tagName;
+- (BOOL)isStartTag;
+- (BOOL)isEndTag;
+- (BOOL)isTag;
@end
-static NSString *VSBeginType = @"BEGIN";
-static NSString *VSEndType = @"END";
-static NSString *VSDataType = @"DATA";
-
@implementation VSSaxTag
-+ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs {
++ (id)beginTag:(NSString *)_tag group:(NSString *)_group
+ attributes:(SaxAttributes *)_attrs
+{
VSSaxTag *tag;
tag = [[[self alloc] init] autorelease];
- tag->type = VSBeginType;
+ tag->type = 'B';
tag->tagName = [_tag copy];
+ tag->group = [_group copy];
tag->attrs = [_attrs retain];
return tag;
}
- (id)initEndTag:(NSString *)_tag {
- self->type = VSEndType;
+ self->type = 'E';
self->tagName = [_tag copy];
return self;
}
-- (id)initWithData:(NSString *)_data {
- self->type = VSDataType;
- self->data = [_data retain];
+- (id)initWithContentString:(NSString *)_data {
+ if (_data == nil) {
+ [self release];
+ return nil;
+ }
+
+ self->datalen = [_data length];
+ self->data = calloc(self->datalen + 1, sizeof(unichar));
+ [_data getCharacters:self->data range:NSMakeRange(0, self->datalen)];
return self;
}
- (void)dealloc {
- [self->data release];
+ if (self->data) free(self->data);
+ [self->group release];
[self->tagName release];
[self->attrs release];
[super dealloc];
}
+/* accessors */
+
- (NSString *)tagName {
return self->tagName;
}
+- (NSString *)group {
+ return self->group;
+}
+
+- (BOOL)isStartTag {
+ return self->type == 'B' ? YES : NO;
+}
+- (BOOL)isEndTag {
+ return self->type == 'E' ? YES : NO;
+}
+- (BOOL)isTag {
+ return (self->type == 'B' || self->type == 'E') ? YES : NO;
+}
@end /* VSSaxTag */
/* parsing */
+- (NSString *)_groupFromTagName:(NSString *)_tagName {
+ NSRange r;
+
+ r = [_tagName rangeOfCharacterFromSet:dotCharSet];
+ if (r.length == 0)
+ return nil;
+
+ return [_tagName substringToIndex:r.location];
+}
+
- (NSString *)_mapTagName:(NSString *)_tagName {
NSString *ret;
NSRange r;
- if ((ret = [self->elementMapping objectForKey:_tagName]) == nil) {
- //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping);
- ret = _tagName;
+ if ((ret = [self->elementMapping objectForKey:_tagName]) != nil)
+ return ret;
- /* This is to allow parsing of vCards produced by Apple
- Addressbook. AFAIK the .dot notation is a non-standard
- extension */
- r = [_tagName rangeOfCharacterFromSet:dotCharSet];
- if (r.length > 0) {
- ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]];
- }
- }
- return ret;
-}
-
-- (void)_addAttribute:(NSString *)_attribute
- value:(NSString *)_value
- toAttrs:(SaxAttributes *)_attrs
-{
- [_attrs addAttribute:_attribute
- uri:self->prefixURI
- rawName:_attribute
- type:@"CDATA"
- value:_value];
-}
-
-- (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value {
- VSSaxTag *element;
+ //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping);
+ ret = _tagName;
- element = [cardStack lastObject];
- [self _addAttribute:_attribute value:_value toAttrs:element->attrs];
+ /*
+ This is to allow parsing of vCards produced by Apple
+ Addressbook.
+ The dot-notation is described as 'grouping' in RFC 2425, section 5.8.2.
+ */
+ r = [_tagName rangeOfCharacterFromSet:dotCharSet];
+ if (r.length > 0)
+ ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]];
+
+ return ret;
}
- (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName {
+ NSDictionary *tagMap;
NSString *mappedName;
+
+ /* check whether we have a attr-map stored under the element-name */
+ tagMap = [self->attributeMapping objectForKey:_tagName];
+ if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
+ return mappedName;
- mappedName = [(NSDictionary *)[self->attributeMapping objectForKey:_tagName]
- objectForKey:_attrName];
- if (mappedName == nil) {
- mappedName = [(NSDictionary *)[self->attributeMapping objectForKey:
- [self _mapTagName:_tagName]]
- objectForKey:_attrName];
- }
- if (mappedName == nil) {
- mappedName = [(NSDictionary *)[self->attributeMapping objectForKey:@""]
- objectForKey:_attrName];
- }
- if (mappedName == nil)
- mappedName = _attrName;
+ /* check whether we have a attr-map stored under the mapped element-name */
+ tagMap = [self->attributeMapping objectForKey:[self _mapTagName:_tagName]];
+ if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
+ return mappedName;
+
+ /* check whether we have a global attr-map */
+ tagMap = [self->attributeMapping objectForKey:@""];
+ if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
+ return mappedName;
- return mappedName;
+ /* return the name as-is */
+ return _attrName;
}
- (void)_parseAttr:(NSString *)_attr
attrEnum = [attributes keyEnumerator];
while ((curAttr = [attrEnum nextObject]) != nil) {
- [self _addAttribute:curAttr
- value:[attributes objectForKey:curAttr]
- toAttrs:retAttrs];
+ [retAttrs addAttribute:curAttr uri:self->prefixURI rawName:curAttr
+ type:@"CDATA" value:[attributes objectForKey:curAttr]];
}
[attributes release];
return retAttrs;
}
-- (VSSaxTag *)_beginTag:(NSString *)_tagName withAttrs:(SaxAttributes *)_attrs{
+- (VSSaxTag *)_beginTag:(NSString *)_tagName group:(NSString *)_group
+ withAttrs:(SaxAttributes *)_attrs
+{
VSSaxTag *tag;
- tag = [VSSaxTag beginTag:_tagName attributes:_attrs];
+ tag = [VSSaxTag beginTag:_tagName group:_group attributes:_attrs];
[self->elementList addObject:tag];
return tag;
}
[tag release]; tag = nil;
}
-- (void)_addSubItems:(NSArray *)_items withData:(NSString *)_content {
+- (void)_addSubItems:(NSArray *)_items group:(NSString *)_group
+ withData:(NSString *)_content
+{
NSEnumerator *itemEnum, *contentEnum;
NSString *subTag;
subContent = [contentEnum nextObject];
- [self _beginTag:subTag withAttrs:nil];
+ [self _beginTag:subTag group:_group withAttrs:nil];
if ([subContent length] > 0) {
VSSaxTag *a;
- if ((a = [(VSSaxTag*)[VSSaxTag alloc] initWithData:subContent]) != nil) {
+ a = [(VSSaxTag*)[VSSaxTag alloc] initWithContentString:subContent];
+ if (a != nil) {
[self->elementList addObject:a];
[a release];
}
}
}
-- (void)_dataTag:(NSString *)_tagName
+- (void)_reportContentAsTag:(NSString *)_tagName
+ group:(NSString *)_group
withAttrs:(SaxAttributes *)_attrs
andContent:(NSString *)_content
{
+ /*
+ This is called for all non-BEGIN|END types.
+ */
NSArray *subItems;
_content = [stringFormatter stringByUnescapingRFC2445Text:_content];
+
+ /* check whether type should be reported as an attribute in XML */
if ([self->attributeElements containsObject:_tagName]) {
- [self _addAttribute:_tagName value:_content];
+ /*
+ Add tag as an attribute to last component in the cardstack. This is
+ stuff like the "VERSION" type contained in a "BEGIN:VCARD" which will
+ be reported as <vcard version=""> (as an attribute of the container).
+ */
+ VSSaxTag *element;
+
+ element = [self->cardStack lastObject];
+ [element->attrs addAttribute:_tagName uri:self->prefixURI
+ rawName:_tagName type:@"CDATA" value:_content];
return;
- }
+ }
+
+ /* report type as an XML tag */
+
+ [self _beginTag:_tagName group:_group withAttrs:_attrs];
- [self _beginTag:_tagName withAttrs:_attrs];
if ([_content length] > 0) {
if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) {
- [self _addSubItems:subItems withData:_content];
+ [self _addSubItems:subItems group:_group withData:_content];
}
else {
VSSaxTag *a;
-
- if ((a = [(VSSaxTag *)[VSSaxTag alloc] initWithData:_content]) != nil) {
+
+ a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString:_content];
+ if (a != nil) {
[self->elementList addObject:a];
[a release];
}
}
}
+
[self _endTag:_tagName];
}
/* report events for collected elements */
-- (void)_eventsForElements {
+- (void)reportStartGroup:(NSString *)_group {
+ SaxAttributes *attrs;
+
+ attrs = [[SaxAttributes alloc] init];
+ [attrs addAttribute:@"name" uri:self->prefixURI rawName:@"name"
+ type:@"CDATA" value:_group];
+
+ [self->contentHandler startElement:@"group" namespace:self->prefixURI
+ rawName:@"group" attributes:attrs];
+ [attrs release];
+}
+- (void)reportEndGroup {
+ [self->contentHandler endElement:@"group" namespace:self->prefixURI
+ rawName:@"group"];
+}
+
+- (void)reportQueuedTags {
+ /*
+ Why does the parser need the list instead of reporting the events
+ straight away?
+
+ Because some vCard tags like the 'version' are reported as attributes
+ on the container tag. So we have a sequence like:
+ BEGIN:VCARD
+ ...
+ VERSION:3.0
+ which will get reported as:
+ <vcard version="3.0">
+ */
NSEnumerator *enu;
- VSSaxTag *obj;
+ VSSaxTag *tagToReport;
+ NSString *lastGroup;
+ lastGroup = nil;
enu = [self->elementList objectEnumerator];
- while ((obj = [enu nextObject]) != nil) {
- if ([obj->type isEqualToString:VSBeginType]) {
- [self->contentHandler startElement:obj->tagName
+ while ((tagToReport = [enu nextObject]) != nil) {
+ if ([tagToReport isStartTag]) {
+ NSString *tg;
+
+ tg = [tagToReport group];
+ if (![lastGroup isEqualToString:tg] && lastGroup != tg) {
+ if (lastGroup != nil) [self reportEndGroup];
+ ASSIGNCOPY(lastGroup, tg);
+ if (lastGroup != nil) [self reportStartGroup:lastGroup];
+ }
+ }
+
+ if ([tagToReport isStartTag]) {
+ [self->contentHandler startElement:[tagToReport tagName]
namespace:self->prefixURI
- rawName:obj->tagName
- attributes:obj->attrs];
- }
- else if ([obj->type isEqualToString:VSEndType]) {
- [self->contentHandler endElement:obj->tagName
+ rawName:[tagToReport tagName]
+ attributes:tagToReport->attrs];
+ }
+ else if ([tagToReport isEndTag]) {
+ [self->contentHandler endElement:[tagToReport tagName]
namespace:self->prefixURI
- rawName:obj->tagName];
+ rawName:[tagToReport tagName]];
}
else {
- unichar *chardata;
- unsigned len;
-
- // TODO: better move to tag itself?
- len = [obj->data length];
- chardata = calloc(len + 1, sizeof(unichar));
- [obj->data getCharacters:chardata range:NSMakeRange(0, len)];
-
- [self->contentHandler characters:chardata length:len];
- if (chardata != NULL) free(chardata); chardata = NULL;
+ [self->contentHandler characters:tagToReport->data
+ length:tagToReport->datalen];
}
}
- [elementList removeAllObjects];
+
+ /* flush event group */
+ [self->elementList removeAllObjects];
+
+ /* close open groups */
+ if (lastGroup != nil) {
+ [self reportEndGroup];
+ [lastGroup release]; lastGroup = nil;
+ }
}
/* errors */
+- (void)error:(NSString *)_text {
+ SaxParseException *e;
+
+ e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
+ reason:_text
+ userInfo:nil];
+ [self->errorHandler error:e];
+}
- (void)warn:(NSString *)_warn {
SaxParseException *e;
/* parsing raw string */
+- (void)_beginComponentWithValue:(NSString *)tagValue {
+ VSSaxTag *tag;
+
+ tag = [self _beginTag:[self _mapTagName:tagValue]
+ group:nil
+ withAttrs:[[[SaxAttributes alloc] init] autorelease]];
+ [self->cardStack addObject:tag];
+}
+
+- (void)_endComponent:(NSString *)tagName value:(NSString *)tagValue {
+ NSString *mtName;
+
+ mtName = [self _mapTagName:tagValue];
+ if ([self->cardStack count] > 0) {
+ NSString *expectedName;
+
+ expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName];
+ if (![expectedName isEqualToString:mtName]) {
+ NSString *s;
+
+ // TODO: rather report an error?
+ // TODO: setup userinfo dict with details
+ s = [NSString stringWithFormat:
+ @"Found end tag '%@' which does not match expected "
+ @"name '%@'!"
+ @" Tag '%@' has not been closed properly. Given "
+ @"document contains errors!",
+ mtName, expectedName, expectedName];
+ [self error:s];
+
+ /* probably futile attempt to parse anyways */
+ if (debugOn) {
+ NSLog(@"%s trying to fix previous error by inserting bogus end "
+ @"tag.",
+ __PRETTY_FUNCTION__);
+ }
+ [self _endTag:expectedName];
+ [self->cardStack removeLastObject];
+ }
+ }
+ else {
+ // TOOD: generate error?
+ [self error:[@"found end tag without any open tags left: "
+ stringByAppendingString:mtName]];
+ }
+ [self _endTag:mtName];
+ [self->cardStack removeLastObject];
+
+ /* report parsed elements */
+
+ if ([self->cardStack count] == 0)
+ [self reportQueuedTags];
+}
+
- (void)_parseLine:(NSString *)_line {
NSString *tagName, *tagValue;
NSMutableArray *tagAttributes;
range:todoRange];
/* is line well-formed? */
if (r.length == 0) {
- [self warn:[@"got an improper content line! ->\n"
- stringByAppendingString:_line]];
+ [self error:[@"got an improper content line! ->\n"
+ stringByAppendingString:_line]];
return;
}
-
+
+ /* tagname is everything up to a ':' or ';' (value or parameter) */
tagName = [[_line substringToIndex:r.location] uppercaseString];
tagAttributes = [[NSMutableArray alloc] initWithCapacity:16];
- /* possible shortcut: if we spotted a ':', we don't have to do "expensive"
+ /*
+ possible shortcut: if we spotted a ':', we don't have to do "expensive"
argument scanning/processing.
*/
if ([_line characterAtIndex:r.location] != ':') {
- BOOL isAtEnd = NO, isInDquote = NO;
- unsigned start = NSMaxRange(r);
-
+ BOOL isAtEnd = NO;
+ BOOL isInDquote = NO;
+ unsigned start;
+
+ start = NSMaxRange(r);
todoRange = NSMakeRange(start, length - start);
while(!isAtEnd) {
BOOL skip = YES;
r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
options:0
range:todoRange];
+
/* is line well-formed? */
if (r.length == 0 || r.location == 0) {
- [self warn:[@"got an improper content line! ->\n"
- stringByAppendingString:_line]];
+ [self error:[@"got an improper content line! ->\n"
+ stringByAppendingString:_line]];
[tagAttributes release]; tagAttributes = nil;
return;
}
}
}
tagValue = [_line substringFromIndex:NSMaxRange(r)];
-
+
+ /*
+ At this point we have:
+ name: 'BEGIN', 'TEL', 'EMAIL', 'ITEM1.ADR' etc
+ value: ';;;Magdeburg;;;Germany'
+ atributes: ("type=INTERNET", "type=HOME", "type=pref")
+ */
/* process tag */
if ([tagName isEqualToString:@"BEGIN"]) {
- VSSaxTag *tag;
- tag = [self _beginTag:[self _mapTagName:tagValue]
- withAttrs:[[[SaxAttributes alloc] init] autorelease]];
- [self->cardStack addObject:tag];
- }
+ if ([tagAttributes count] > 0)
+ [self warn:@"Losing unexpected parameters of BEGIN line."];
+ [self _beginComponentWithValue:tagValue];
+ }
else if ([tagName isEqualToString:@"END"]) {
- NSString *mtName;
-
- mtName = [self _mapTagName:tagValue];
- if ([self->cardStack count] > 0) {
- NSString *expectedName;
-
- expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName];
- if (![expectedName isEqualToString:mtName]) {
- NSString *s;
-
- // TODO: rather report an error?
- // TODO: setup userinfo dict with details
- s = [NSString stringWithFormat:
- @"Found end tag '%@' which does not match expected "
- @"name '%@'!"
- @" Tag '%@' has not been closed properly. Given "
- @"document contains errors!",
- mtName, expectedName, expectedName];
- [self warn:s];
-
- /* probably futile attempt to parse anyways */
- if (debugOn) {
- NSLog(@"%s trying to fix previous error by inserting bogus end "
- @"tag.",
- __PRETTY_FUNCTION__);
- }
- [self _endTag:expectedName];
- [self->cardStack removeLastObject];
- }
- }
- else {
- // TOOD: generate error?
- [self warn:[@"found end tag without any open tags left: "
- stringByAppendingString:mtName]];
- }
- [self _endTag:mtName];
- [self->cardStack removeLastObject];
-
- /* report parsed elements */
-
- if ([self->cardStack count] == 0)
- [self _eventsForElements];
+ if ([tagAttributes count] > 0)
+ [self warn:@"Losing unexpected parameters of END line."];
+ [self _endComponent:tagName value:tagValue];
}
else {
- [self _dataTag:[self _mapTagName:tagName]
+ [self _reportContentAsTag:[self _mapTagName:tagName]
+ group:[self _groupFromTagName:tagName]
withAttrs:[self _mapAttrs:tagAttributes forTag:tagName]
andContent:tagValue];
}
+
[tagAttributes release];
}
/* top level parsing method */
-- (void)_reportDocStart {
+- (void)reportDocStart {
[self->contentHandler startDocument];
[self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
-
- [self->contentHandler startElement:@"vCardSet" namespace:self->prefixURI
- rawName:@"vCardSet" attributes:nil];
}
-- (void)_reportDocEnd {
- [self->contentHandler endElement:@"vCardSet" namespace:self->prefixURI
- rawName:@"vCardSet"];
-
+- (void)reportDocEnd {
[self->contentHandler endPrefixMapping:@""];
[self->contentHandler endDocument];
}
- (void)_parseString:(NSString *)_rawString {
+ /*
+ This method split the string into content lines for actual vCard
+ parsing.
+
+ RFC2445:
+ contentline = name *(";" param ) ":" value CRLF
+ ; When parsing a content line, folded lines MUST first
+ ; be unfolded
+ */
NSMutableString *line;
unsigned pos, length;
NSRange r;
- [self _reportDocStart];
+ [self reportDocStart];
/* start parsing */
length = [_rawString length];
- /* RFC2445:
- contentline = name *(";" param ) ":" value CRLF
- ; When parsing a content line, folded lines MUST first
- ; be unfolded
- */
- r = NSMakeRange(0, 0);
- /* probably too optimistic */
- line = [[NSMutableString alloc] initWithCapacity:75 + 2];
-
+ r = NSMakeRange(0, 0);
+ line = [[NSMutableString alloc] initWithCapacity:75 + 2];
+
for (pos = 0; pos < length; pos++) {
unichar c;
[line appendString:[_rawString substringWithRange:r]];
[self _parseLine:line];
}
-
+
if ([self->cardStack count] != 0) {
[self warn:@"found elements on cardStack. This indicates an improper "
@"nesting structure! Not all required events will have been "
[line release]; line = nil;
- [self _reportDocEnd];
+ [self reportDocEnd];
}
/* main entry functions */
[self->errorHandler fatalError:e];
return;
}
+
+ /* ensure consistent state */
+
+ [self->cardStack removeAllObjects];
+ [self->elementList removeAllObjects];
/* start parsing */
}
if (_sysId == nil) _sysId = @"<string>";
[self _parseString:_source];
+
+ /* tear down */
+
+ [self->cardStack removeAllObjects];
+ [self->elementList removeAllObjects];
}
- (void)parseFromSource:(id)_source {