Skip to content

Instantly share code, notes, and snippets.

@Shilo
Last active December 22, 2015 03:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shilo/6409813 to your computer and use it in GitHub Desktop.
Save Shilo/6409813 to your computer and use it in GitHub Desktop.
Customizable string XML parser for NSString, NSMutableString, NSAttributedString, and NSMutableAttributedString.
//
// 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
//
// 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