diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj
index 63c1e95..c3ede11 100644
--- a/HTMLKit.xcodeproj/project.pbxproj
+++ b/HTMLKit.xcodeproj/project.pbxproj
@@ -26,6 +26,9 @@
625A150819C78ABA00AD0C32 /* HTMLInputStreamReader.h in Headers */ = {isa = PBXBuildFile; fileRef = 625A150619C78ABA00AD0C32 /* HTMLInputStreamReader.h */; };
625A150919C78ABA00AD0C32 /* HTMLInputStreamReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 625A150719C78ABA00AD0C32 /* HTMLInputStreamReader.m */; };
625A150A19C78ABA00AD0C32 /* HTMLInputStreamReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 625A150719C78ABA00AD0C32 /* HTMLInputStreamReader.m */; };
+ 625A150D19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 625A150B19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.h */; };
+ 625A150E19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 625A150C19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m */; };
+ 625A150F19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 625A150C19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -79,6 +82,8 @@
625A14EC19C7834100AD0C32 /* HTMLKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HTMLKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
625A150619C78ABA00AD0C32 /* HTMLInputStreamReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLInputStreamReader.h; sourceTree = ""; };
625A150719C78ABA00AD0C32 /* HTMLInputStreamReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLInputStreamReader.m; sourceTree = ""; };
+ 625A150B19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLInputStreamReaderErrors.h; sourceTree = ""; };
+ 625A150C19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLInputStreamReaderErrors.m; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -170,6 +175,8 @@
625A14BD19C7829400AD0C32 /* HTMLKit.m */,
625A150619C78ABA00AD0C32 /* HTMLInputStreamReader.h */,
625A150719C78ABA00AD0C32 /* HTMLInputStreamReader.m */,
+ 625A150B19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.h */,
+ 625A150C19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m */,
625A14B619C7829400AD0C32 /* Supporting Files */,
);
name = "Source Code";
@@ -212,6 +219,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 625A150D19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.h in Headers */,
625A150419C783EB00AD0C32 /* HTMLKit.h in Headers */,
625A150819C78ABA00AD0C32 /* HTMLInputStreamReader.h in Headers */,
);
@@ -359,6 +367,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 625A150F19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m in Sources */,
625A150A19C78ABA00AD0C32 /* HTMLInputStreamReader.m in Sources */,
625A14BE19C7829400AD0C32 /* HTMLKit.m in Sources */,
);
@@ -376,6 +385,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 625A150E19C78BE900AD0C32 /* HTMLInputStreamReaderErrors.m in Sources */,
625A150919C78ABA00AD0C32 /* HTMLInputStreamReader.m in Sources */,
625A150219C783DE00AD0C32 /* HTMLKit.m in Sources */,
);
diff --git a/HTMLKit/HTMLInputStreamReader.h b/HTMLKit/HTMLInputStreamReader.h
index 8b8213d..f59d394 100644
--- a/HTMLKit/HTMLInputStreamReader.h
+++ b/HTMLKit/HTMLInputStreamReader.h
@@ -7,8 +7,7 @@
//
#import
-
-typedef void (^ HTMLStreamReaderErrorCallback)(NSError *error);
+#import "HTMLInputStreamReaderErrors.h"
/**
* HTML Input Stream Reader processor conforming to the HTML standard
diff --git a/HTMLKit/HTMLInputStreamReader.m b/HTMLKit/HTMLInputStreamReader.m
index 01e7938..e373791 100644
--- a/HTMLKit/HTMLInputStreamReader.m
+++ b/HTMLKit/HTMLInputStreamReader.m
@@ -8,10 +8,57 @@
#import "HTMLInputStreamReader.h"
+#pragma mark Constants & Inlines
+
static UTF32Char const REPLACEMENT = 0xFFFD;
static UTF32Char const LINE_FEED = 0x000A;
static UTF32Char const CARRIAGE_RETURN = 0x000D;
+NS_INLINE BOOL isControlOrUndefinedCharacter(UTF32Char character)
+{
+ return ((character >= 0x0001 && character <= 0x0008) ||
+ (character >= 0x000E && character <= 0x001F) ||
+ (character >= 0x007F && character <= 0x009F) ||
+ (character >= 0xFDD0 && character <= 0xFDEF) ||
+ character == 0x000B ||
+ character == 0xFFFE ||
+ character == 0xFFFF ||
+ character == 0x1FFFE ||
+ character == 0x1FFFF ||
+ character == 0x2FFFE ||
+ character == 0x2FFFF ||
+ character == 0x3FFFE ||
+ character == 0x3FFFF ||
+ character == 0x4FFFE ||
+ character == 0x4FFFF ||
+ character == 0x5FFFE ||
+ character == 0x5FFFF ||
+ character == 0x6FFFE ||
+ character == 0x6FFFF ||
+ character == 0x7FFFE ||
+ character == 0x7FFFF ||
+ character == 0x8FFFE ||
+ character == 0x8FFFF ||
+ character == 0x9FFFE ||
+ character == 0x9FFFF ||
+ character == 0xAFFFE ||
+ character == 0xAFFFF ||
+ character == 0xBFFFE ||
+ character == 0xBFFFF ||
+ character == 0xCFFFE ||
+ character == 0xCFFFF ||
+ character == 0xDFFFE ||
+ character == 0xDFFFF ||
+ character == 0xEFFFE ||
+ character == 0xEFFFF ||
+ character == 0xFFFFE ||
+ character == 0xFFFFF ||
+ character == 0x10FFFE ||
+ character == 0x10FFFF);
+}
+
+#pragma mark - HTMLInputStreamReader
+
@interface HTMLInputStreamReader ()
{
NSString *_string;
@@ -19,7 +66,7 @@ static UTF32Char const CARRIAGE_RETURN = 0x000D;
CFStringInlineBuffer _buffer;
NSUInteger _location;
UTF32Char _currentInputCharacter;
-
+ NSUInteger _consume;
HTMLStreamReaderErrorCallback _errorCallback;
}
@end
@@ -52,20 +99,58 @@ static UTF32Char const CARRIAGE_RETURN = 0x000D;
{
if (_location >= _string.length) return EOF;
+ _consume = 1;
UTF32Char nextInputCharacter = CFStringGetCharacterFromInlineBuffer(&_buffer, _location);
if (nextInputCharacter == 0) return EOF;
- if (nextInputCharacter == CARRIAGE_RETURN) return LINE_FEED;
- if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) return REPLACEMENT;
+ if (nextInputCharacter == CARRIAGE_RETURN) {
+ UniChar next = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
+ if (next == LINE_FEED) _consume++;
+ return LINE_FEED;
+ }
+ if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) {
+ [HTMLInputStreamReaderErrors reportParseError:HTMLStreamReaderErrorIsolatedLowSurrogate
+ atLocation:_location
+ andCallback:_errorCallback];
+ return REPLACEMENT;
+ }
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
- if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) return REPLACEMENT;
+ if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
+ [HTMLInputStreamReaderErrors reportParseError:HTMLStreamReaderErrorIsolatedHighSurrogate
+ atLocation:_location
+ andCallback:_errorCallback];
+ return REPLACEMENT;
+ }
nextInputCharacter = CFStringGetLongCharacterForSurrogatePair(nextInputCharacter, surrogateLow);
}
+ if (isControlOrUndefinedCharacter(nextInputCharacter)) {
+ [HTMLInputStreamReaderErrors reportParseError:HTMLStreamReaderErrorControlOrUndefined
+ atLocation:_location
+ andCallback:_errorCallback];
+ }
+
return nextInputCharacter;
}
+- (UTF32Char)consumeNextInputCharacter
+{
+ UTF32Char nextInputCharacter = [self nextInputCharacter];
+ _location += _consume;
+ _scanner.scanLocation = _location;
+ _currentInputCharacter = nextInputCharacter;
+
+ return nextInputCharacter;
+}
+
+- (void)unconsumeCurrentInputCharacter
+{
+ _location -= _consume;
+ _scanner.scanLocation = _location;
+ _consume = 0;
+}
+
@end
diff --git a/HTMLKit/HTMLInputStreamReaderErrors.h b/HTMLKit/HTMLInputStreamReaderErrors.h
new file mode 100644
index 0000000..4bbc760
--- /dev/null
+++ b/HTMLKit/HTMLInputStreamReaderErrors.h
@@ -0,0 +1,28 @@
+//
+// HTMLInputStreamReaderErrors.h
+// HTMLKit
+//
+// Created by Iska on 15/09/14.
+// Copyright (c) 2014 BrainCookie. All rights reserved.
+//
+
+#import
+
+extern NSString * const HTMLStreamReaderErrorDomain;
+
+typedef NS_ENUM(NSUInteger, HTMLStreamReaderError)
+{
+ HTMLStreamReaderErrorIsolatedLowSurrogate = 100,
+ HTMLStreamReaderErrorIsolatedHighSurrogate = 200,
+ HTMLStreamReaderErrorControlOrUndefined = 300
+};
+
+typedef void (^ HTMLStreamReaderErrorCallback)(NSError *error);
+
+@interface HTMLInputStreamReaderErrors : NSObject
+
++ (void)reportParseError:(HTMLStreamReaderError)parseError
+ atLocation:(NSUInteger)location
+ andCallback:(HTMLStreamReaderErrorCallback)callback;
+
+@end
diff --git a/HTMLKit/HTMLInputStreamReaderErrors.m b/HTMLKit/HTMLInputStreamReaderErrors.m
new file mode 100644
index 0000000..e6399c5
--- /dev/null
+++ b/HTMLKit/HTMLInputStreamReaderErrors.m
@@ -0,0 +1,44 @@
+//
+// HTMLInputStreamReaderErrors.m
+// HTMLKit
+//
+// Created by Iska on 15/09/14.
+// Copyright (c) 2014 BrainCookie. All rights reserved.
+//
+
+#import "HTMLInputStreamReaderErrors.h"
+
+NSString * const HTMLStreamReaderErrorDomain = @"HTMLStreamReaderErrorDomain";
+
+static inline NSString * ReasonStringForError(HTMLStreamReaderError error)
+{
+ switch (error) {
+ case HTMLStreamReaderErrorIsolatedLowSurrogate:
+ return @"Non-Unicode character found (an isolated low surrogate)";
+ case HTMLStreamReaderErrorIsolatedHighSurrogate:
+ return @"Non-Unicode character found (an isolated high surrogate)";
+ case HTMLStreamReaderErrorControlOrUndefined:
+ return @"A control/undefined character found";
+ default:
+ break;
+ }
+}
+
+@implementation HTMLInputStreamReaderErrors
+
++ (void)reportParseError:(HTMLStreamReaderError)parseError atLocation:(NSUInteger)location andCallback:(HTMLStreamReaderErrorCallback)callback
+{
+ if (callback == nil) return;
+
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"HTML Stream parse error at [%ld]", location],
+ NSLocalizedFailureReasonErrorKey : ReasonStringForError(parseError)
+ };
+
+ NSError *error = [[NSError alloc] initWithDomain:HTMLStreamReaderErrorDomain
+ code:parseError
+ userInfo:userInfo];
+ callback(error);
+}
+
+@end