Last active
December 22, 2015 03:19
-
-
Save Shilo/6409813 to your computer and use it in GitHub Desktop.
Customizable string XML parser for NSString, NSMutableString, NSAttributedString, and NSMutableAttributedString.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NSString+XML.h | |
// Customizable string XML parser for NSString, NSMutableString, NSAttributedString, and NSMutableAttributedString. | |
// | |
// Created by Shilo White on 8/31/13. | |
// Copyright (c) 2013 XIDA Design & Technik. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
@protocol NSAttributedStringParserDelegate <NSObject> | |
@optional | |
- (void)attributedString:(NSAttributedString *)attributedString didEndTagWithName:(NSString *)tagName attributes:(NSDictionary *)attributes children:(NSArray *)children range:(NSRange)range; | |
- (void)attributedString:(NSAttributedString *)attributedString didEndCommentTagWithRange:(NSRange)range; | |
- (BOOL)attributedString:(NSAttributedString *)attributedString emptyTagWithName:(NSString *)tagName; | |
- (void)attributedStringDidEndParsing:(NSAttributedString *)attributedString; | |
- (NSString *)customLeftBracketForAttributedString:(NSAttributedString *)attributedString; | |
- (NSString *)customRightBracketForAttributedString:(NSAttributedString *)attributedString; | |
- (NSString *)customCommentStartForAttributedString:(NSAttributedString *)attributedString; | |
- (NSString *)customCommentEndForAttributedString:(NSAttributedString *)attributedString; | |
- (unichar)customEndTagCharForAttributedString:(NSAttributedString *)attributedString; | |
@end | |
@protocol NSMutableAttributedStringParserDelegate <NSAttributedStringParserDelegate> | |
@optional | |
- (void)mutableAttributedString:(NSMutableAttributedString *)mutableAttributedString didEndTagWithName:(NSString *)tagName attributes:(NSDictionary *)attributes children:(NSArray *)children range:(NSRange)range; | |
- (void)mutableAttributedString:(NSMutableAttributedString *)mutableAttributedString didEndCommentTagWithRange:(NSRange)range; | |
- (NSString *)mutableAttributedString:(NSMutableAttributedString *)mutableAttributedString stringToReplaceTagWithName:(NSString *)tagName; | |
- (NSString *)stringToReplaceCommentTagForMutableAttributedString:(NSMutableAttributedString *)mutableAttributedString; | |
- (void)mutableAttributedStringDidCondenseWhitespaceAndNewlineCharacters:(NSMutableAttributedString *)mutableAttributedString; | |
@end | |
@protocol NSStringParserDelegate <NSObject> | |
@optional | |
- (void)string:(NSString *)string didEndTagWithName:(NSString *)tagName attributes:(NSDictionary *)attributes children:(NSArray *)children range:(NSRange)range; | |
- (void)string:(NSString *)string didEndCommentTagWithRange:(NSRange)range; | |
- (BOOL)string:(NSString *)string emptyTagWithName:(NSString *)tagName; | |
- (void)stringDidEndParsing:(NSString *)string; | |
- (NSString *)customLeftBracketForString:(NSString *)string; | |
- (NSString *)customRightBracketForString:(NSString *)string; | |
- (NSString *)customCommentStartForString:(NSString *)string; | |
- (NSString *)customCommentEndForString:(NSString *)string; | |
- (unichar)customEndTagCharForString:(NSString *)string; | |
@end | |
@protocol NSMutableStringParserDelegate <NSStringParserDelegate> | |
@optional | |
- (void)mutableString:(NSMutableString *)mutableString didEndTagWithName:(NSString *)tagName attributes:(NSDictionary *)attributes children:(NSArray *)children range:(NSRange)range; | |
- (void)mutableString:(NSMutableString *)mutableString didEndCommentTagWithRange:(NSRange)range; | |
- (NSString *)mutableString:(NSMutableString *)mutableString stringToReplaceTagWithName:(NSString *)tagName; | |
- (NSString *)stringToReplaceCommentTagForMutableString:(NSMutableString *)mutableString; | |
- (void)mutableStringDidCondenseWhitespaceAndNewlineCharacters:(NSMutableString *)mutableString; | |
@end | |
typedef enum | |
{ | |
NSStringParserBracketTypeAngle = 0, | |
NSStringParserBracketTypeSquare, | |
NSStringParserBracketTypeCurly, | |
NSStringParserBracketTypeParenthesis, | |
NSStringParserBracketTypeCustom | |
} NSStringParserBracketType; | |
typedef enum | |
{ | |
NSStringParserEndTagCharTypeForwardSlash = 0, | |
NSStringParserEndTagCharTypeBackSlash, | |
NSStringParserEndTagCharTypeCustom | |
} NSStringParserEndTagCharType; | |
typedef enum | |
{ | |
NSStringParserCommentTypeHTML = 0, | |
NSStringParserCommentTypeScript, | |
NSStringParserCommentTypeCustom | |
} NSStringParserCommentType; | |
@protocol ParsableString <NSObject> | |
- (void)parse; | |
- (NSUInteger)occurrencesOfTagWithName:(NSString *)tagName childrenLevel:(NSUInteger)childrenLevel; | |
@property (nonatomic, weak) id parserDelegate; | |
@property (nonatomic, assign) NSStringParserBracketType parserBracketType; | |
@property (nonatomic, assign) NSStringParserEndTagCharType parserEndTagCharType; | |
@property (nonatomic, assign) NSStringParserCommentType parserCommentType; | |
@property (nonatomic, assign) BOOL parserCaseSensitive; | |
@property (nonatomic, assign) BOOL parserReversed; | |
@property (nonatomic, assign) BOOL parserReplaceTagsLast; | |
@end | |
@protocol ParsableMutableString <ParsableString> | |
@property (nonatomic, assign) BOOL parserCondenseWhitespaceAndNewlineCharacters; | |
@property (nonatomic, readonly) NSString *parserCondenseSafeNewlineString; | |
@end | |
@interface NSAttributedString (XML) <ParsableString> | |
@property (nonatomic, weak) id<NSAttributedStringParserDelegate> parserDelegate; | |
@end | |
@interface NSMutableAttributedString (XML) <ParsableMutableString> | |
@property (nonatomic, weak) id<NSMutableAttributedStringParserDelegate> parserDelegate; | |
@end | |
@interface NSString (XML) <ParsableString> | |
@property (nonatomic, weak) id<NSStringParserDelegate> parserDelegate; | |
@end | |
@interface NSMutableString (XML) <ParsableMutableString> | |
@property (nonatomic, weak) id<NSMutableStringParserDelegate> parserDelegate; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NSString+XML.m | |
// Customizable string XML parser for NSString, NSMutableString, NSAttributedString, and NSMutableAttributedString. | |
// | |
// Created by Shilo White on 8/31/13. | |
// Copyright (c) 2013 XIDA Design & Technik. All rights reserved. | |
// | |
#import "NSString+XML.h" | |
#import <objc/runtime.h> | |
#define BRACKET_ANGLE_LEFT @"<" | |
#define BRACKET_ANGLE_RIGHT @">" | |
#define BRACKET_SQUARE_LEFT @"[" | |
#define BRACKET_SQUARE_RIGHT @"]" | |
#define BRACKET_CURLY_LEFT @"{" | |
#define BRACKET_CURLY_RIGHT @"}" | |
#define BRACKET_PARENTHESIS_LEFT @"(" | |
#define BRACKET_PARENTHESIS_RIGHT @")" | |
#define COMMENT_HTML_START @"!--" | |
#define COMMENT_HTML_END @"--" | |
#define COMMENT_SCRIPT_START @"/*" | |
#define COMMENT_SCRIPT_END @"*/" | |
#define TAG_COMPONENTS_SEPARATOR @" " | |
#define TAG_ATTRIBUTES_SEPARATOR @"=" | |
#define END_TAG_CHAR_FORWARD_SLASH '/' | |
#define END_TAG_CHAR_BACK_SLASH '\\' | |
#define DEFAULT_PARSER_REVERSED YES | |
#define DEFAULT_PARSER_REPLACE_TAGS_LAST NO | |
#define DEFAULT_PARSER_CONDENSE_WHITESPACE_AND_NEWLINE_CHARACTERS NO | |
#define CONDENSE_SAFE_NEWLINE_CHAR 0x7f | |
@protocol NSInternalStringParserDelegate <NSMutableAttributedStringParserDelegate, NSMutableStringParserDelegate> | |
@end | |
@interface NSString (InternalXML) | |
+ (void)parseString:(id<ParsableMutableString>)stringObject; | |
+ (NSUInteger)occurrencesOfTagWithName:(NSString *)tagName inString:(id<ParsableMutableString>)stringObject childrenLevel:(NSUInteger)childrenLevel; | |
+ (NSString *)parserCondenseSafeNewlineStringForString:(id<ParsableMutableString>)stringObject; | |
- (NSArray *)componentsSeparatedByString:(NSString *)separator limit:(NSUInteger)limit; | |
@end | |
@implementation NSAttributedString (XML) | |
- (void)parse | |
{ | |
[NSString parseString:(id<ParsableMutableString>)self]; | |
} | |
- (NSUInteger)occurrencesOfTagWithName:(NSString *)tagName childrenLevel:(NSUInteger)childrenLevel | |
{ | |
return [NSString occurrencesOfTagWithName:tagName inString:(id<ParsableMutableString>)self childrenLevel:childrenLevel]; | |
} | |
- (void)setParserDelegate:(__weak id<NSAttributedStringParserDelegate>)parserDelegate | |
{ | |
objc_setAssociatedObject(self, @selector(parserDelegate), parserDelegate, OBJC_ASSOCIATION_ASSIGN); | |
} | |
- (id<NSAttributedStringParserDelegate>)parserDelegate | |
{ | |
return objc_getAssociatedObject(self, @selector(parserDelegate)); | |
} | |
- (void)setParserBracketType:(NSStringParserBracketType)parserBracketType | |
{ | |
objc_setAssociatedObject(self, @selector(parserBracketType), [NSNumber numberWithInt:parserBracketType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (NSStringParserBracketType)parserBracketType | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserBracketType)) intValue]; | |
} | |
- (void)setParserEndTagCharType:(NSStringParserEndTagCharType)parserEndTagCharType | |
{ | |
objc_setAssociatedObject(self, @selector(parserEndTagCharType), [NSNumber numberWithInt:parserEndTagCharType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (NSStringParserEndTagCharType)parserEndTagCharType | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserEndTagCharType)) intValue]; | |
} | |
- (void)setParserCommentType:(NSStringParserCommentType)parserCommentType | |
{ | |
objc_setAssociatedObject(self, @selector(parserCommentType), [NSNumber numberWithInt:parserCommentType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (NSStringParserCommentType)parserCommentType | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserCommentType)) intValue]; | |
} | |
- (void)setParserCaseSensitive:(BOOL)parserCaseSensitive | |
{ | |
objc_setAssociatedObject(self, @selector(parserCaseSensitive), [NSNumber numberWithBool:parserCaseSensitive], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserCaseSensitive | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserCaseSensitive)) boolValue]; | |
} | |
- (void)setParserReversed:(BOOL)parserReversed | |
{ | |
objc_setAssociatedObject(self, @selector(parserReversed), [NSNumber numberWithBool:parserReversed], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserReversed | |
{ | |
NSNumber *parserReversed = objc_getAssociatedObject(self, @selector(parserReversed)); | |
return (parserReversed)?[parserReversed boolValue]:DEFAULT_PARSER_REVERSED; | |
} | |
- (void)setParserReplaceTagsLast:(BOOL)parserReplaceTagsLast | |
{ | |
objc_setAssociatedObject(self, @selector(parserReplaceTagsLast), [NSNumber numberWithBool:parserReplaceTagsLast], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserReplaceTagsLast | |
{ | |
NSNumber *parserReplaceTagsLast = objc_getAssociatedObject(self, @selector(parserReplaceTagsLast)); | |
return (parserReplaceTagsLast)?[parserReplaceTagsLast boolValue]:DEFAULT_PARSER_REPLACE_TAGS_LAST; | |
} | |
- (void)setParserCondenseWhitespaceAndNewlineCharacters:(BOOL)parserCondenseWhitespaceAndNewlineCharacters | |
{ | |
objc_setAssociatedObject(self, @selector(parserCondenseWhitespaceAndNewlineCharacters), [NSNumber numberWithBool:parserCondenseWhitespaceAndNewlineCharacters], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserCondenseWhitespaceAndNewlineCharacters | |
{ | |
NSNumber *parserCondenseWhitespaceAndNewlineCharacters = objc_getAssociatedObject(self, @selector(parserCondenseWhitespaceAndNewlineCharacters)); | |
return (parserCondenseWhitespaceAndNewlineCharacters)?[parserCondenseWhitespaceAndNewlineCharacters boolValue]:DEFAULT_PARSER_CONDENSE_WHITESPACE_AND_NEWLINE_CHARACTERS; | |
} | |
- (NSString *)parserCondenseSafeNewlineString | |
{ | |
return [NSString parserCondenseSafeNewlineStringForString:(id<ParsableMutableString>)self]; | |
} | |
@end | |
@implementation NSString (XML) | |
- (void)parse | |
{ | |
[NSString parseString:(id<ParsableMutableString>)self]; | |
} | |
- (NSUInteger)occurrencesOfTagWithName:(NSString *)tagName childrenLevel:(NSUInteger)childrenLevel | |
{ | |
return [NSString occurrencesOfTagWithName:tagName inString:(id<ParsableMutableString>)self childrenLevel:childrenLevel]; | |
} | |
- (void)setParserDelegate:(__weak id<NSStringParserDelegate>)parserDelegate | |
{ | |
objc_setAssociatedObject(self, @selector(parserDelegate), parserDelegate, OBJC_ASSOCIATION_ASSIGN); | |
} | |
- (id<NSStringParserDelegate>)parserDelegate | |
{ | |
return objc_getAssociatedObject(self, @selector(parserDelegate)); | |
} | |
- (void)setParserBracketType:(NSStringParserBracketType)parserBracketType | |
{ | |
objc_setAssociatedObject(self, @selector(parserBracketType), [NSNumber numberWithInt:parserBracketType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (NSStringParserBracketType)parserBracketType | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserBracketType)) intValue]; | |
} | |
- (void)setParserEndTagCharType:(NSStringParserEndTagCharType)parserEndTagCharType | |
{ | |
objc_setAssociatedObject(self, @selector(parserEndTagCharType), [NSNumber numberWithInt:parserEndTagCharType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (NSStringParserEndTagCharType)parserEndTagCharType | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserEndTagCharType)) intValue]; | |
} | |
- (void)setParserCommentType:(NSStringParserCommentType)parserCommentType | |
{ | |
objc_setAssociatedObject(self, @selector(parserCommentType), [NSNumber numberWithInt:parserCommentType], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (NSStringParserCommentType)parserCommentType | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserCommentType)) intValue]; | |
} | |
- (void)setParserCaseSensitive:(BOOL)parserCaseSensitive | |
{ | |
objc_setAssociatedObject(self, @selector(parserCaseSensitive), [NSNumber numberWithBool:parserCaseSensitive], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserCaseSensitive | |
{ | |
return [objc_getAssociatedObject(self, @selector(parserCaseSensitive)) boolValue]; | |
} | |
- (void)setParserReversed:(BOOL)parserReversed | |
{ | |
objc_setAssociatedObject(self, @selector(parserReversed), [NSNumber numberWithBool:parserReversed], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserReversed | |
{ | |
NSNumber *parserReversed = objc_getAssociatedObject(self, @selector(parserReversed)); | |
return (parserReversed)?[parserReversed boolValue]:DEFAULT_PARSER_REVERSED; | |
} | |
- (void)setParserReplaceTagsLast:(BOOL)parserReplaceTagsLast | |
{ | |
objc_setAssociatedObject(self, @selector(parserReplaceTagsLast), [NSNumber numberWithBool:parserReplaceTagsLast], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserReplaceTagsLast | |
{ | |
NSNumber *parserReplaceTagsLast = objc_getAssociatedObject(self, @selector(parserReplaceTagsLast)); | |
return (parserReplaceTagsLast)?[parserReplaceTagsLast boolValue]:DEFAULT_PARSER_REPLACE_TAGS_LAST; | |
} | |
- (void)setParserCondenseWhitespaceAndNewlineCharacters:(BOOL)parserCondenseWhitespaceAndNewlineCharacters | |
{ | |
objc_setAssociatedObject(self, @selector(parserCondenseWhitespaceAndNewlineCharacters), [NSNumber numberWithBool:parserCondenseWhitespaceAndNewlineCharacters], OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (BOOL)parserCondenseWhitespaceAndNewlineCharacters | |
{ | |
NSNumber *parserCondenseWhitespaceAndNewlineCharacters = objc_getAssociatedObject(self, @selector(parserCondenseWhitespaceAndNewlineCharacters)); | |
return (parserCondenseWhitespaceAndNewlineCharacters)?[parserCondenseWhitespaceAndNewlineCharacters boolValue]:DEFAULT_PARSER_CONDENSE_WHITESPACE_AND_NEWLINE_CHARACTERS; | |
} | |
- (NSString *)parserCondenseSafeNewlineString | |
{ | |
return [NSString parserCondenseSafeNewlineStringForString:(id<ParsableMutableString>)self]; | |
} | |
@end | |
@implementation NSString (InternalXML) | |
+ (void)parseString:(id<ParsableMutableString>)stringObject | |
{ | |
id<NSInternalStringParserDelegate> delegate = (id<NSInternalStringParserDelegate>)stringObject.parserDelegate; | |
NSMutableDictionary *openTags = [[NSMutableDictionary alloc] init]; | |
NSArray *children = [NSArray arrayWithObjects:[NSNull null], [[NSMutableArray alloc] init], nil]; | |
NSMutableArray *childrenHierarchy = [[NSMutableArray alloc] init]; | |
[childrenHierarchy addObject:children]; | |
NSString *leftBracket = [self leftBracketWithDelegate:delegate stringObject:stringObject]; | |
NSString *rightBracket = [self rightBracketWithDelegate:delegate stringObject:stringObject]; | |
NSString *commentStart = [self commentStartWithDelegate:delegate stringObject:stringObject]; | |
NSString *commentEnd = [self commentEndWithDelegate:delegate stringObject:stringObject]; | |
unichar endTagChar = [self endTagCharWithDelegate:delegate stringObject:stringObject]; | |
NSUInteger leftBracketLength = leftBracket.length; | |
NSUInteger rightBracketLength = rightBracket.length; | |
NSUInteger stringOffset = 0; | |
BOOL parserReversed = stringObject.parserReversed; | |
BOOL parserReplaceTagsLast = stringObject.parserReplaceTagsLast; | |
NSString *scannerString = nil; | |
if ([stringObject isKindOfClass:[NSAttributedString class]]) | |
scannerString = ((NSAttributedString *)stringObject).string; | |
else if ([stringObject isKindOfClass:[NSString class]]) | |
scannerString = (NSString *)stringObject; | |
NSScanner *scanner = [NSScanner scannerWithString:(stringObject.parserCaseSensitive)?scannerString:scannerString.lowercaseString]; | |
scanner.charactersToBeSkipped = nil; | |
while (!scanner.isAtEnd) | |
{ | |
NSString *tagContents = nil; | |
NSString *tagName = nil; | |
BOOL isEndTag = NO; | |
NSUInteger wholeTagLength = 0; | |
[scanner scanUpToString:leftBracket intoString:nil]; | |
if (scanner.isAtEnd) break; | |
NSUInteger tagLocation = scanner.scanLocation; | |
scanner.scanLocation += leftBracketLength; | |
scanner.charactersToBeSkipped = [NSCharacterSet whitespaceAndNewlineCharacterSet]; | |
if ([scanner scanString:commentStart intoString:nil]) | |
{ | |
NSUInteger commentTagLocation = tagLocation+stringOffset; | |
while (!scanner.isAtEnd) | |
{ | |
[scanner scanUpToString:commentEnd intoString:nil]; | |
if (scanner.isAtEnd) break; | |
scanner.scanLocation += commentEnd.length; | |
if ([scanner scanString:rightBracket intoString:nil]) | |
{ | |
scanner.charactersToBeSkipped = nil; | |
NSUInteger commentTagLength = scanner.scanLocation-tagLocation; | |
NSString *tagReplacement = [self stringToReplaceCommentTagWithDelegate:delegate stringObject:stringObject]; | |
if (tagReplacement) | |
{ | |
[(NSMutableString *)stringObject replaceCharactersInRange:NSMakeRange(commentTagLocation, commentTagLength) withString:tagReplacement]; | |
stringOffset += tagReplacement.length - commentTagLength; | |
commentTagLength = tagReplacement.length; | |
} | |
[self didEndCommentTagWithRange:NSMakeRange(commentTagLocation, commentTagLength) delegate:delegate pending:parserReversed stringObject:stringObject]; | |
break; | |
} | |
} | |
if (!scanner.isAtEnd) | |
continue; | |
else | |
{ | |
NSUInteger commentTagLength = scanner.scanLocation-commentTagLocation+stringOffset; | |
NSString *tagReplacement = [self stringToReplaceCommentTagWithDelegate:delegate stringObject:stringObject]; | |
if (tagReplacement) | |
{ | |
[(NSMutableString *)stringObject replaceCharactersInRange:NSMakeRange(commentTagLocation, commentTagLength) withString:tagReplacement]; | |
stringOffset += tagReplacement.length - commentTagLength; | |
commentTagLength = tagReplacement.length; | |
} | |
[self didEndCommentTagWithRange:NSMakeRange(commentTagLocation, commentTagLength) delegate:delegate pending:parserReversed stringObject:stringObject]; | |
break; | |
} | |
} | |
scanner.charactersToBeSkipped = nil; | |
[scanner scanUpToString:rightBracket intoString:&tagContents]; | |
if (scanner.isAtEnd) break; | |
scanner.scanLocation += rightBracketLength; | |
wholeTagLength = tagContents.length+leftBracketLength+rightBracketLength; | |
tagContents = [self contentsWithTrimmedWhitespaceAndNewlineForTagWithContents:tagContents]; | |
tagName = [self nameForTagWithContents:tagContents endTagChar:endTagChar isEndTag:&isEndTag]; | |
NSString *tagReplacement = [self stringToReplaceTagWithName:tagName delegate:delegate stringObject:stringObject]; | |
if (tagReplacement) [self replaceTagWithString:tagReplacement scanLocation:scanner.scanLocation tagLength:&wholeTagLength leftBracketLength:leftBracketLength rightBracketLength:rightBracketLength stringOffset:&stringOffset stringObject:stringObject pending:parserReplaceTagsLast]; | |
if (!isEndTag) | |
{ | |
BOOL isEmptyTag = NO; | |
NSString *attributeString = [self attributeStringForTagWithContents:tagContents endTagChar:endTagChar isEmptyTag:&isEmptyTag]; | |
NSDictionary *tagAttributes = [self attributesForTagWithAttributeString:attributeString]; | |
NSUInteger tagValueLocation = scanner.scanLocation+stringOffset; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if (!isEmptyTag && [delegate respondsToSelector:@selector(attributedString:emptyTagWithName:)]) | |
isEmptyTag = [delegate attributedString:(NSAttributedString *)stringObject emptyTagWithName:tagName]; | |
} | |
else | |
{ | |
if (!isEmptyTag && [delegate respondsToSelector:@selector(string:emptyTagWithName:)]) | |
isEmptyTag = [delegate string:(NSString *)stringObject emptyTagWithName:tagName]; | |
} | |
NSArray *tagChildren = [NSArray arrayWithObjects:tagName, [[NSMutableArray alloc] init], nil]; | |
NSArray *parentTag = [childrenHierarchy lastObject]; | |
[[parentTag objectAtIndex:1] addObject:tagChildren]; | |
if (!isEmptyTag) | |
{ | |
[childrenHierarchy addObject:tagChildren]; | |
NSMutableArray *tags = [openTags objectForKey:tagName]; | |
if (!tags) | |
{ | |
tags = [[NSMutableArray alloc] init]; | |
[openTags setObject:tags forKey:tagName]; | |
} | |
[tags addObject:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:tagValueLocation], tagAttributes, [tagChildren objectAtIndex:1], nil]]; | |
} | |
else | |
{ | |
[self didEndTagWithName:tagName attributes:tagAttributes range:NSMakeRange(tagValueLocation, 0) delegate:delegate pending:parserReversed stringObject:stringObject children:nil]; | |
} | |
} | |
else | |
{ | |
NSMutableArray *tags = [openTags objectForKey:tagName]; | |
NSArray *tagInfo = [tags lastObject]; | |
NSUInteger tagValueLocation = 0; | |
NSUInteger tagValueLength = 0; | |
NSDictionary *tagAttributes = nil; | |
NSArray *tagChildren = nil; | |
if (tagInfo) | |
{ | |
tagValueLocation = [[tagInfo objectAtIndex:0] unsignedIntegerValue]; | |
tagValueLength = scanner.scanLocation-tagValueLocation-wholeTagLength+stringOffset; | |
tagAttributes = [tagInfo objectAtIndex:1]; | |
tagChildren = [tagInfo objectAtIndex:2]; | |
[tags removeLastObject]; | |
} | |
else | |
{ | |
tagValueLocation = scanner.scanLocation+stringOffset; | |
} | |
[self didEndTagWithName:tagName attributes:tagAttributes range:NSMakeRange(tagValueLocation, tagValueLength) delegate:delegate pending:parserReversed stringObject:stringObject children:tagChildren]; | |
[childrenHierarchy removeLastObject]; | |
} | |
} | |
for (NSString *openTag in openTags) | |
{ | |
for (NSArray *tagInfo in [[openTags objectForKey:openTag] reverseObjectEnumerator]) | |
{ | |
NSUInteger tagValueLocation = [[tagInfo objectAtIndex:0] unsignedIntegerValue]; | |
NSUInteger tagValueLength = scanner.scanLocation-tagValueLocation+stringOffset; | |
NSDictionary *tagAttributes = [tagInfo objectAtIndex:1]; | |
NSArray *tagChildren = [tagInfo objectAtIndex:2]; | |
[self didEndTagWithName:openTag attributes:tagAttributes range:NSMakeRange(tagValueLocation, tagValueLength) delegate:delegate pending:parserReversed stringObject:stringObject children:tagChildren]; | |
} | |
} | |
if (parserReversed) | |
{ | |
for (NSArray *tagInfo in [[self pendingClosedTags] reverseObjectEnumerator]) | |
{ | |
if (tagInfo.count == 1) | |
{ | |
NSRange commentRange = NSRangeFromString([tagInfo objectAtIndex:0]); | |
[self didEndCommentTagWithRange:commentRange delegate:delegate pending:NO stringObject:stringObject]; | |
} | |
else | |
{ | |
NSString *tagName = [tagInfo objectAtIndex:0]; | |
NSDictionary *tagAttributes = [tagInfo objectAtIndex:1]; | |
NSRange tagRange = NSRangeFromString([tagInfo objectAtIndex:2]); | |
NSArray *children = (tagInfo.count>3)?[tagInfo objectAtIndex:3]:nil; | |
if (!children.count) children = nil; | |
[self didEndTagWithName:tagName attributes:tagAttributes range:tagRange delegate:delegate pending:NO stringObject:stringObject children:children]; | |
} | |
} | |
[self setPendingClosedTags:nil]; | |
} | |
if (parserReplaceTagsLast) | |
{ | |
NSUInteger relativeStringOffset = 0; | |
for (NSArray *tagReplacementInfo in [self pendingTagReplacement]) | |
{ | |
NSString *tagReplacement = [tagReplacementInfo objectAtIndex:0]; | |
NSUInteger scanLocation = [[tagReplacementInfo objectAtIndex:1] unsignedIntegerValue]; | |
NSUInteger tagLength = [[tagReplacementInfo objectAtIndex:2] unsignedIntegerValue]; | |
NSUInteger leftBracketLength = [[tagReplacementInfo objectAtIndex:3] unsignedIntegerValue]; | |
NSUInteger rightBracketLength = [[tagReplacementInfo objectAtIndex:4] unsignedIntegerValue]; | |
NSUInteger stringOffset = [[tagReplacementInfo objectAtIndex:5] unsignedIntegerValue]; | |
NSUInteger offset = stringOffset+relativeStringOffset; | |
NSUInteger oldOffset = offset; | |
if (tagReplacement) [self replaceTagWithString:tagReplacement scanLocation:scanLocation tagLength:&tagLength leftBracketLength:leftBracketLength rightBracketLength:rightBracketLength stringOffset:&offset stringObject:stringObject pending:NO]; | |
relativeStringOffset += offset-oldOffset; | |
} | |
[self setPendingTagReplacement:nil]; | |
} | |
if (stringObject.parserCondenseWhitespaceAndNewlineCharacters && ([self isMutableAttributedString:stringObject] || [self isMutableString:stringObject])) | |
[self condenseWhitespaceAndNewlineCharactersForStringObject:(NSMutableString *)stringObject delegate:delegate]; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(attributedStringDidEndParsing:)]) | |
[delegate attributedStringDidEndParsing:(NSAttributedString *)stringObject]; | |
} | |
else | |
{ | |
if ([delegate respondsToSelector:@selector(stringDidEndParsing:)]) | |
[delegate stringDidEndParsing:(NSString *)stringObject]; | |
} | |
} | |
+ (void)didEndTagWithName:(NSString *)tagName attributes:(NSDictionary *)attributes range:(NSRange)range delegate:(id<NSInternalStringParserDelegate>)delegate pending:(BOOL)pending stringObject:(id<ParsableMutableString>)stringObject children:(NSArray *)children | |
{ | |
if (pending) | |
{ | |
[self addPendingClosedTagWithName:tagName attributes:attributes range:range children:children]; | |
return; | |
} | |
if (!attributes.count) attributes = nil; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([self isMutableAttributedString:stringObject] && [delegate respondsToSelector:@selector(mutableAttributedString:didEndTagWithName:attributes:children:range:)]) | |
{ | |
[delegate mutableAttributedString:(NSMutableAttributedString *)stringObject didEndTagWithName:tagName attributes:attributes children:children range:range]; | |
return; | |
} | |
if ([delegate respondsToSelector:@selector(attributedString:didEndTagWithName:attributes:children:range:)]) | |
[delegate attributedString:(NSAttributedString *)stringObject didEndTagWithName:tagName attributes:attributes children:children range:range]; | |
} | |
else | |
{ | |
if ([self isMutableString:stringObject] && [delegate respondsToSelector:@selector(mutableString:didEndTagWithName:attributes:children:range:)]) | |
{ | |
[delegate mutableString:(NSMutableString *)stringObject didEndTagWithName:tagName attributes:attributes children:children range:range]; | |
return; | |
} | |
if ([delegate respondsToSelector:@selector(string:didEndTagWithName:attributes:children:range:)]) | |
[delegate string:(NSString *)stringObject didEndTagWithName:tagName attributes:attributes children:children range:range]; | |
} | |
} | |
+ (void)didEndCommentTagWithRange:(NSRange)range delegate:(id<NSInternalStringParserDelegate>)delegate pending:(BOOL)pending stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
if (pending) | |
{ | |
[self addPendingCommentTagWithRange:range]; | |
return; | |
} | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([self isMutableAttributedString:stringObject] && [delegate respondsToSelector:@selector(mutableAttributedString:didEndCommentTagWithRange:)]) | |
{ | |
[delegate mutableAttributedString:(NSMutableAttributedString *)stringObject didEndCommentTagWithRange:range]; | |
return; | |
} | |
if ([delegate respondsToSelector:@selector(attributedString:didEndCommentTagWithRange:)]) | |
[delegate attributedString:(NSAttributedString *)stringObject didEndCommentTagWithRange:range]; | |
} | |
else | |
{ | |
if ([self isMutableString:stringObject] && [delegate respondsToSelector:@selector(mutableString:didEndCommentTagWithRange:)]) | |
{ | |
[delegate mutableString:(NSMutableString *)stringObject didEndCommentTagWithRange:range]; | |
return; | |
} | |
if ([delegate respondsToSelector:@selector(string:didEndCommentTagWithRange:)]) | |
[delegate string:(NSString *)stringObject didEndCommentTagWithRange:range]; | |
} | |
} | |
+ (NSString *)nameForTagWithContents:(NSString *)tagContents endTagChar:(unichar)endTagChar isEndTag:(BOOL *)isEndTag | |
{ | |
*isEndTag = ([tagContents characterAtIndex:0] == endTagChar); | |
for (NSString *component in [self componentsForTagWithContents:tagContents endTagChar:endTagChar]) | |
if (component.length) | |
return component; | |
return nil; | |
} | |
+ (NSString *)stringToReplaceTagWithName:(NSString *)tagName delegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSString *tagReplacement = nil; | |
if ([self isMutableAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(mutableAttributedString:stringToReplaceTagWithName:)]) | |
tagReplacement = [delegate mutableAttributedString:(NSMutableAttributedString *)stringObject stringToReplaceTagWithName:tagName]; | |
} | |
else if ([self isMutableString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(mutableString:stringToReplaceTagWithName:)]) | |
tagReplacement = [delegate mutableString:(NSMutableString *)stringObject stringToReplaceTagWithName:tagName]; | |
} | |
return tagReplacement; | |
} | |
+ (void)condenseWhitespaceAndNewlineCharactersForStringObject:(NSMutableString<ParsableMutableString> *)stringObject delegate:(id<NSInternalStringParserDelegate>)delegate | |
{ | |
NSString *scannerString = nil; | |
if ([stringObject isKindOfClass:[NSAttributedString class]]) | |
scannerString = ((NSAttributedString *)stringObject).string; | |
else if ([stringObject isKindOfClass:[NSString class]]) | |
scannerString = (NSString *)stringObject; | |
NSUInteger stringOffset = 0; | |
NSScanner *scanner = [NSScanner scannerWithString:scannerString]; | |
scanner.charactersToBeSkipped = nil; | |
NSCharacterSet *newlineCharacterSet = [NSCharacterSet newlineCharacterSet]; | |
NSCharacterSet *whitespaceAndNewlineCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; | |
NSString *condenseSafeNewlineString = [NSString stringWithFormat:@"%c", CONDENSE_SAFE_NEWLINE_CHAR]; | |
while (!scanner.isAtEnd) | |
{ | |
NSString *line = nil; | |
NSUInteger lineLocation = scanner.scanLocation+stringOffset; | |
[scanner scanUpToCharactersFromSet:newlineCharacterSet intoString:&line]; | |
NSUInteger lineStringOffset = 0; | |
NSScanner *lineScanner = [NSScanner scannerWithString:line]; | |
lineScanner.charactersToBeSkipped = nil; | |
while (!lineScanner.isAtEnd) | |
{ | |
[lineScanner scanUpToCharactersFromSet:whitespaceAndNewlineCharacterSet intoString:nil]; | |
if (lineScanner.isAtEnd) break; | |
NSInteger whitespaceAndNewlineCharacterLocation = lineScanner.scanLocation; | |
if ([lineScanner scanCharactersFromSet:whitespaceAndNewlineCharacterSet intoString:nil]) | |
{ | |
NSInteger whitespaceAndNewlineCharacterLength = lineScanner.scanLocation-whitespaceAndNewlineCharacterLocation; | |
NSString *whiteSpaceAndNewlineReplacementString = ((whitespaceAndNewlineCharacterLocation) && (lineScanner.scanLocation < line.length))?@" ":@""; | |
[stringObject replaceCharactersInRange:NSMakeRange(whitespaceAndNewlineCharacterLocation+lineLocation+lineStringOffset, whitespaceAndNewlineCharacterLength) withString:whiteSpaceAndNewlineReplacementString]; | |
lineStringOffset -= whitespaceAndNewlineCharacterLength-whiteSpaceAndNewlineReplacementString.length; | |
} | |
} | |
NSInteger lineLength = line.length+lineStringOffset; | |
if (lineLength>0 && lineLocation>0) | |
{ | |
[stringObject replaceCharactersInRange:NSMakeRange(lineLocation, 0) withString:@" "]; | |
lineStringOffset += 1; | |
} | |
stringOffset += lineStringOffset; | |
if (scanner.isAtEnd) break; | |
NSInteger newLineCharactersLocation = scanner.scanLocation+stringOffset; | |
if ([scanner scanCharactersFromSet:newlineCharacterSet intoString:nil]) | |
{ | |
NSInteger newLineCharactersLength = scanner.scanLocation+stringOffset-newLineCharactersLocation; | |
[stringObject replaceCharactersInRange:NSMakeRange(newLineCharactersLocation, newLineCharactersLength) withString:@""]; | |
stringOffset -= newLineCharactersLength; | |
} | |
} | |
if ([stringObject isKindOfClass:[NSAttributedString class]]) | |
scannerString = ((NSAttributedString *)stringObject).string; | |
else if ([stringObject isKindOfClass:[NSString class]]) | |
scannerString = (NSString *)stringObject; | |
int condenseSafeNewlineStringOffset = 0; | |
NSScanner *condenseSafeNewlineScanner = [NSScanner scannerWithString:scannerString]; | |
while (!condenseSafeNewlineScanner.isAtEnd) | |
{ | |
[condenseSafeNewlineScanner scanUpToString:condenseSafeNewlineString intoString:nil]; | |
if (condenseSafeNewlineScanner.isAtEnd) break; | |
BOOL leftWhitespace = ((condenseSafeNewlineScanner.scanLocation > 0) && ([scannerString characterAtIndex:condenseSafeNewlineScanner.scanLocation-1+condenseSafeNewlineStringOffset] == ' ')); | |
BOOL rightWhitespace = ((condenseSafeNewlineScanner.scanLocation+condenseSafeNewlineStringOffset < scannerString.length-1) && ([scannerString characterAtIndex:condenseSafeNewlineScanner.scanLocation+1+condenseSafeNewlineStringOffset] == ' ')); | |
[stringObject replaceCharactersInRange:NSMakeRange(condenseSafeNewlineScanner.scanLocation+condenseSafeNewlineStringOffset-leftWhitespace, condenseSafeNewlineString.length+leftWhitespace+rightWhitespace) withString:@"\n"]; | |
condenseSafeNewlineScanner.scanLocation += condenseSafeNewlineString.length + rightWhitespace; | |
condenseSafeNewlineStringOffset -= leftWhitespace+rightWhitespace; | |
} | |
if ([self isMutableAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(mutableAttributedStringDidCondenseWhitespaceAndNewlineCharacters:)]) | |
[delegate mutableAttributedStringDidCondenseWhitespaceAndNewlineCharacters:(NSMutableAttributedString *)stringObject]; | |
} | |
else if ([self isMutableString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(mutableStringDidCondenseWhitespaceAndNewlineCharacters:)]) | |
[delegate mutableStringDidCondenseWhitespaceAndNewlineCharacters:(NSMutableString *)stringObject]; | |
} | |
} | |
+ (NSString *)parserCondenseSafeNewlineStringForString:(id<ParsableMutableString>)stringObject | |
{ | |
if (stringObject.parserCondenseWhitespaceAndNewlineCharacters) | |
return [NSString stringWithFormat:@"%c", CONDENSE_SAFE_NEWLINE_CHAR]; | |
else | |
return @"\n"; | |
} | |
+ (void)replaceTagWithString:(NSString *)tagReplacement scanLocation:(NSUInteger)scanLocation tagLength:(NSUInteger *)tagLength leftBracketLength:(NSUInteger)leftBracketLength rightBracketLength:(NSUInteger)rightBracketLength stringOffset:(NSUInteger *)stringOffset stringObject:(id<ParsableMutableString>)stringObject pending:(BOOL)pending | |
{ | |
if (pending) | |
{ | |
[self addPendingReplaceTagWithString:tagReplacement scanLocation:scanLocation tagLength:*tagLength leftBracketLength:leftBracketLength rightBracketLength:rightBracketLength stringOffset:*stringOffset]; | |
return; | |
} | |
NSUInteger tagLocation = scanLocation - *tagLength; | |
[(NSMutableString *)stringObject replaceCharactersInRange:NSMakeRange(tagLocation + *stringOffset, *tagLength) withString:tagReplacement]; | |
*stringOffset += tagReplacement.length - *tagLength; | |
*tagLength = tagReplacement.length; | |
} | |
+ (void)addPendingReplaceTagWithString:(NSString *)tagReplacement scanLocation:(NSUInteger)scanLocation tagLength:(NSUInteger)tagLength leftBracketLength:(NSUInteger)leftBracketLength rightBracketLength:(NSUInteger)rightBracketLength stringOffset:(NSUInteger)stringOffset | |
{ | |
if (![self pendingTagReplacement]) | |
[self setPendingTagReplacement:[[NSMutableArray alloc] init]]; | |
NSArray *tagReplacementInfo = [NSArray arrayWithObjects:tagReplacement, [NSNumber numberWithUnsignedInteger:scanLocation], [NSNumber numberWithUnsignedInteger:tagLength], [NSNumber numberWithUnsignedInteger:leftBracketLength], [NSNumber numberWithUnsignedInteger:rightBracketLength], [NSNumber numberWithUnsignedInteger:stringOffset], nil]; | |
[[self pendingTagReplacement] addObject:tagReplacementInfo]; | |
} | |
+ (NSString *)attributeStringForTagWithContents:(NSString *)tagContents endTagChar:(unichar)endTagChar isEmptyTag:(BOOL *)isEmptyTag | |
{ | |
*isEmptyTag = ([tagContents characterAtIndex:tagContents.length-1] == endTagChar); | |
NSArray *tagComponents = [self componentsForTagWithContents:tagContents endTagChar:endTagChar limit:1]; | |
return (tagComponents.count>1) ? [tagComponents objectAtIndex:1] : nil; | |
} | |
+ (NSDictionary *)attributesForTagWithAttributeString:(NSString *)tagAttributeString | |
{ | |
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; | |
NSString *lastName = nil; | |
NSMutableString *lastValue = nil; | |
unichar lastQuote; | |
BOOL iteratedNameFirst = NO; | |
for (NSString *attributeComponent in [self attributeComponentsForTagWithAttributeString:tagAttributeString]) | |
{ | |
if (!attributeComponent.length) continue; | |
if (!iteratedNameFirst) | |
{ | |
iteratedNameFirst = YES; | |
lastName = attributeComponent; | |
} | |
else | |
{ | |
BOOL iteratedValue = NO; | |
for (NSString *attributeComponent2 in [attributeComponent componentsSeparatedByString:TAG_COMPONENTS_SEPARATOR]) | |
{ | |
if (!attributeComponent2.length) continue; | |
if (!iteratedValue) | |
{ | |
if (lastValue) | |
{ | |
[lastValue appendFormat:@"%@%@", TAG_COMPONENTS_SEPARATOR, attributeComponent2]; | |
if ([attributeComponent2 characterAtIndex:attributeComponent2.length-1] == lastQuote) | |
{ | |
[attributes setObject:[self valueWithTrimmedQuotesForAttributeWithValue:lastValue] forKey:lastName]; | |
lastName = nil; | |
lastValue = nil; | |
iteratedValue = YES; | |
} | |
} | |
else | |
{ | |
if ([attributeComponent2 characterAtIndex:0] == '\'' || [attributeComponent2 characterAtIndex:0] == '"') | |
{ | |
if ([attributeComponent2 characterAtIndex:attributeComponent2.length-1] == [attributeComponent2 characterAtIndex:0]) | |
{ | |
[attributes setObject:[self valueWithTrimmedQuotesForAttributeWithValue:attributeComponent2] forKey:lastName]; | |
lastName = nil; | |
iteratedValue = YES; | |
} | |
else | |
{ | |
lastQuote = [attributeComponent2 characterAtIndex:0]; | |
lastValue = [[NSMutableString alloc] initWithString:attributeComponent2]; | |
} | |
} | |
else | |
{ | |
[attributes setObject:[self valueWithTrimmedQuotesForAttributeWithValue:attributeComponent2] forKey:lastName]; | |
lastName = nil; | |
iteratedValue = YES; | |
} | |
} | |
} | |
else | |
{ | |
if (lastName) | |
[attributes setObject:@"" forKey:lastName]; | |
lastName = attributeComponent2; | |
iteratedValue = YES; | |
} | |
} | |
} | |
} | |
if (lastName) | |
[attributes setObject:@"" forKey:lastName]; | |
return attributes; | |
} | |
+ (NSString *)contentsWithTrimmedWhitespaceAndNewlineForTagWithContents:(NSString *)tagContents | |
{ | |
return [tagContents stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; | |
} | |
+ (NSString *)contentsWithTrimmedEndTagChar:(unichar)endTagChar forTagWithContents:(NSString *)tagContents | |
{ | |
return [tagContents stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:[NSString stringWithCharacters:&endTagChar length:1]]]; | |
} | |
+ (NSString *)valueWithTrimmedQuotesForAttributeWithValue:(NSString *)attributeValue | |
{ | |
return [attributeValue stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"'\""]]; | |
} | |
+ (NSArray *)componentsForTagWithContents:(NSString *)tagContents endTagChar:(unichar)endTagChar | |
{ | |
return [[self contentsWithTrimmedEndTagChar:endTagChar forTagWithContents:tagContents] componentsSeparatedByString:TAG_COMPONENTS_SEPARATOR]; | |
} | |
+ (NSArray *)componentsForTagWithContents:(NSString *)tagContents endTagChar:(unichar)endTagChar limit:(NSUInteger)limit | |
{ | |
return [[self contentsWithTrimmedEndTagChar:endTagChar forTagWithContents:tagContents] componentsSeparatedByString:TAG_COMPONENTS_SEPARATOR limit:limit]; | |
} | |
+ (NSArray *)attributeComponentsForTagWithAttributeString:(NSString *)tagAttributeString | |
{ | |
return [[self contentsWithTrimmedWhitespaceAndNewlineForTagWithContents:tagAttributeString] componentsSeparatedByString:TAG_ATTRIBUTES_SEPARATOR]; | |
} | |
+ (NSString *)leftBracketWithDelegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSStringParserBracketType bracketType = stringObject.parserBracketType; | |
if (bracketType == NSStringParserBracketTypeCustom) | |
{ | |
NSString *customLeftBracket = nil; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(customLeftBracketForAttributedString:)]) | |
customLeftBracket = [delegate customLeftBracketForAttributedString:(NSAttributedString *)stringObject]; | |
} | |
else | |
{ | |
if ([delegate respondsToSelector:@selector(customLeftBracketForString:)]) | |
customLeftBracket = [delegate customLeftBracketForString:(NSString *)stringObject]; | |
} | |
return (customLeftBracket)?customLeftBracket:BRACKET_ANGLE_LEFT; | |
} | |
return (bracketType == NSStringParserBracketTypeSquare)?BRACKET_SQUARE_LEFT: | |
(bracketType == NSStringParserBracketTypeCurly)?BRACKET_CURLY_LEFT: | |
(bracketType == NSStringParserBracketTypeParenthesis)?BRACKET_PARENTHESIS_LEFT: | |
BRACKET_ANGLE_LEFT; | |
} | |
+ (NSString *)rightBracketWithDelegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSStringParserBracketType bracketType = stringObject.parserBracketType; | |
if (bracketType == NSStringParserBracketTypeCustom) | |
{ | |
NSString *customRightBracket = nil; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(customRightBracketForAttributedString:)]) | |
customRightBracket = [delegate customRightBracketForAttributedString:(NSAttributedString *)stringObject]; | |
} | |
else | |
{ | |
if ([delegate respondsToSelector:@selector(customRightBracketForString:)]) | |
customRightBracket = [delegate customRightBracketForString:(NSString *)stringObject]; | |
} | |
return (customRightBracket)?customRightBracket:BRACKET_ANGLE_RIGHT; | |
} | |
return (bracketType == NSStringParserBracketTypeSquare)?BRACKET_SQUARE_RIGHT: | |
(bracketType == NSStringParserBracketTypeCurly)?BRACKET_CURLY_RIGHT: | |
(bracketType == NSStringParserBracketTypeParenthesis)?BRACKET_PARENTHESIS_RIGHT: | |
BRACKET_ANGLE_RIGHT; | |
} | |
+ (NSString *)commentStartWithDelegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSStringParserCommentType commentType = stringObject.parserCommentType; | |
if (commentType == NSStringParserCommentTypeCustom) | |
{ | |
NSString *customCommentStart = nil; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(customCommentStartForAttributedString:)]) | |
customCommentStart = [delegate customCommentStartForAttributedString:(NSAttributedString *)stringObject]; | |
} | |
else | |
{ | |
if ([delegate respondsToSelector:@selector(customCommentStartForString:)]) | |
customCommentStart = [delegate customCommentStartForString:(NSString *)stringObject]; | |
} | |
return (customCommentStart)?customCommentStart:COMMENT_HTML_START; | |
} | |
return (commentType == NSStringParserCommentTypeScript)?COMMENT_SCRIPT_START: | |
COMMENT_HTML_START; | |
} | |
+ (NSString *)commentEndWithDelegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSStringParserCommentType commentType = stringObject.parserCommentType; | |
if (commentType == NSStringParserCommentTypeCustom) | |
{ | |
NSString *customCommentEnd = nil; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(customCommentEndForAttributedString:)]) | |
customCommentEnd = [delegate customCommentEndForAttributedString:(NSAttributedString *)stringObject]; | |
} | |
else | |
{ | |
if ([delegate respondsToSelector:@selector(customCommentEndForString:)]) | |
customCommentEnd = [delegate customCommentEndForString:(NSString *)stringObject]; | |
} | |
return (customCommentEnd)?customCommentEnd:COMMENT_HTML_END; | |
} | |
return (commentType == NSStringParserCommentTypeScript)?COMMENT_SCRIPT_END: | |
COMMENT_HTML_END; | |
} | |
+ (unichar)endTagCharWithDelegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSStringParserEndTagCharType endTagCharType = stringObject.parserEndTagCharType; | |
if (endTagCharType == NSStringParserEndTagCharTypeCustom) | |
{ | |
unichar customEndTagChar = END_TAG_CHAR_FORWARD_SLASH; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(customEndTagCharForAttributedString:)]) | |
customEndTagChar = [delegate customEndTagCharForAttributedString:(NSAttributedString *)stringObject]; | |
} | |
else | |
{ | |
if ([delegate respondsToSelector:@selector(customEndTagCharForString:)]) | |
customEndTagChar = [delegate customEndTagCharForString:(NSString *)stringObject]; | |
} | |
return customEndTagChar; | |
} | |
return (endTagCharType == NSStringParserEndTagCharTypeBackSlash)?END_TAG_CHAR_BACK_SLASH: | |
END_TAG_CHAR_FORWARD_SLASH; | |
} | |
+ (NSString *)stringToReplaceCommentTagWithDelegate:(id<NSInternalStringParserDelegate>)delegate stringObject:(id<ParsableMutableString>)stringObject | |
{ | |
NSString *tagReplacement = nil; | |
if ([self isMutableAttributedString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(stringToReplaceCommentTagForMutableAttributedString:)]) | |
tagReplacement = [delegate stringToReplaceCommentTagForMutableAttributedString:(NSMutableAttributedString *)stringObject]; | |
} | |
else if ([self isMutableString:stringObject]) | |
{ | |
if ([delegate respondsToSelector:@selector(stringToReplaceCommentTagForMutableString:)]) | |
tagReplacement = [delegate stringToReplaceCommentTagForMutableString:(NSMutableString *)stringObject]; | |
} | |
return tagReplacement; | |
} | |
+ (void)addPendingClosedTagWithName:(NSString *)tagName attributes:(NSDictionary *)attributes range:(NSRange)range children:(NSArray *)children | |
{ | |
if (![self pendingClosedTags]) | |
[self setPendingClosedTags:[[NSMutableArray alloc] init]]; | |
NSArray *tagInfo = [NSArray arrayWithObjects:tagName, attributes, NSStringFromRange(range), children, nil]; | |
[[self pendingClosedTags] addObject:tagInfo]; | |
} | |
+ (void)addPendingCommentTagWithRange:(NSRange)range | |
{ | |
if (![self pendingClosedTags]) | |
[self setPendingClosedTags:[[NSMutableArray alloc] init]]; | |
NSArray *tagInfo = [NSArray arrayWithObject:NSStringFromRange(range)]; | |
[[self pendingClosedTags] addObject:tagInfo]; | |
} | |
+ (void)setPendingClosedTags:(NSMutableArray *)pendingClosedTags | |
{ | |
objc_setAssociatedObject(self, @selector(pendingClosedTags), pendingClosedTags, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
+ (NSMutableArray *)pendingClosedTags | |
{ | |
return objc_getAssociatedObject(self, @selector(pendingClosedTags)); | |
} | |
+ (void)setPendingTagReplacement:(NSMutableArray *)pendingTagReplacement | |
{ | |
objc_setAssociatedObject(self, @selector(pendingTagReplacement), pendingTagReplacement, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
+ (NSMutableArray *)pendingTagReplacement | |
{ | |
return objc_getAssociatedObject(self, @selector(pendingTagReplacement)); | |
} | |
+ (BOOL)isMutableAttributedString:(id)string | |
{ | |
return ([string isKindOfClass:[NSMutableAttributedString class]]); | |
} | |
+ (BOOL)isMutableString:(id)string | |
{ | |
//hack to check for mutability (requires the correct assigned protocol) - isKindOfClass: always returns YES | |
return (([string isKindOfClass:[NSMutableString class]]) && [((id<ParsableMutableString>)string).parserDelegate conformsToProtocol:@protocol(NSMutableStringParserDelegate)]); | |
} | |
+ (BOOL)isAttributedString:(id)string | |
{ | |
return ([string isKindOfClass:[NSAttributedString class]]); | |
} | |
- (NSArray *)componentsSeparatedByString:(NSString *)separator limit:(NSUInteger)limit | |
{ | |
if (limit == 0) | |
return [self componentsSeparatedByString:separator]; | |
NSArray *allComponents = [self componentsSeparatedByString:separator]; | |
NSMutableArray *components = [NSMutableArray arrayWithCapacity:MIN(limit, allComponents.count)]; | |
int i = 0; | |
for (NSString *component in allComponents) | |
{ | |
if (i >= limit) | |
{ | |
[components addObject:[[allComponents subarrayWithRange:NSMakeRange(i, allComponents.count-1)] componentsJoinedByString:separator]]; | |
break; | |
} | |
[components addObject:component]; | |
i++; | |
} | |
return components; | |
} | |
+ (NSUInteger)occurrencesOfTagWithName:(NSString *)tagName inString:(id<ParsableMutableString>)stringObject childrenLevel:(NSUInteger)childrenLevel | |
{ | |
NSUInteger occurrences = 0; | |
NSUInteger curChildrenLevel = 0; | |
id<NSInternalStringParserDelegate> delegate = (id<NSInternalStringParserDelegate>)stringObject.parserDelegate; | |
NSString *leftBracket = [self leftBracketWithDelegate:delegate stringObject:stringObject]; | |
NSString *rightBracket = [self rightBracketWithDelegate:delegate stringObject:stringObject]; | |
NSString *commentStart = [self commentStartWithDelegate:delegate stringObject:stringObject]; | |
NSString *commentEnd = [self commentEndWithDelegate:delegate stringObject:stringObject]; | |
unichar endTagChar = [self endTagCharWithDelegate:delegate stringObject:stringObject]; | |
NSUInteger leftBracketLength = leftBracket.length; | |
NSUInteger rightBracketLength = rightBracket.length; | |
NSMutableArray *openTags = [[NSMutableArray alloc] init]; | |
NSString *scannerString = nil; | |
if ([stringObject isKindOfClass:[NSAttributedString class]]) | |
scannerString = ((NSAttributedString *)stringObject).string; | |
else if ([stringObject isKindOfClass:[NSString class]]) | |
scannerString = (NSString *)stringObject; | |
NSScanner *scanner = [NSScanner scannerWithString:(stringObject.parserCaseSensitive)?scannerString:scannerString.lowercaseString]; | |
scanner.charactersToBeSkipped = nil; | |
while (!scanner.isAtEnd) | |
{ | |
NSString *tagContents = nil; | |
BOOL isEndTag = NO; | |
[scanner scanUpToString:leftBracket intoString:nil]; | |
if (scanner.isAtEnd) break; | |
scanner.scanLocation += leftBracketLength; | |
scanner.charactersToBeSkipped = [NSCharacterSet whitespaceAndNewlineCharacterSet]; | |
if ([scanner scanString:commentStart intoString:nil]) | |
{ | |
while (!scanner.isAtEnd) | |
{ | |
[scanner scanUpToString:commentEnd intoString:nil]; | |
if (scanner.isAtEnd) break; | |
scanner.scanLocation += commentEnd.length; | |
if ([scanner scanString:rightBracket intoString:nil]) | |
{ | |
scanner.charactersToBeSkipped = nil; | |
break; | |
} | |
} | |
if (!scanner.isAtEnd) | |
continue; | |
else | |
break; | |
} | |
scanner.charactersToBeSkipped = nil; | |
[scanner scanUpToString:rightBracket intoString:&tagContents]; | |
if (scanner.isAtEnd) break; | |
scanner.scanLocation += rightBracketLength; | |
tagContents = [self contentsWithTrimmedWhitespaceAndNewlineForTagWithContents:tagContents]; | |
NSString *name = [self nameForTagWithContents:tagContents endTagChar:endTagChar isEndTag:&isEndTag]; | |
if (!isEndTag) | |
{ | |
BOOL isEmptyTag = NO; | |
if ([self isAttributedString:stringObject]) | |
{ | |
if (!isEmptyTag && [delegate respondsToSelector:@selector(attributedString:emptyTagWithName:)]) | |
isEmptyTag = [delegate attributedString:(NSAttributedString *)stringObject emptyTagWithName:tagName]; | |
} | |
else | |
{ | |
if (!isEmptyTag && [delegate respondsToSelector:@selector(string:emptyTagWithName:)]) | |
isEmptyTag = [delegate string:(NSString *)stringObject emptyTagWithName:tagName]; | |
} | |
if ((!childrenLevel || curChildrenLevel<childrenLevel) && [tagName isEqualToString:name]) | |
occurrences++; | |
if (!isEmptyTag) | |
{ | |
[openTags addObject:tagName]; | |
curChildrenLevel++; | |
} | |
} | |
else | |
{ | |
if ([[openTags lastObject] isEqualToString:tagName]) | |
{ | |
[openTags removeLastObject]; | |
curChildrenLevel--; | |
} | |
} | |
} | |
return occurrences; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment