Created
December 24, 2015 10:01
-
-
Save hiroshi-maybe/cb2989c2e704ec71de6c to your computer and use it in GitHub Desktop.
Simple flat tag parser
This file contains hidden or 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
| class Tag { | |
| let tagName: NSString | |
| let element: NSString | |
| init(tagName: NSString, element: NSString) { | |
| self.tagName = tagName | |
| self.element = element | |
| } | |
| static let open: NSString = "<" | |
| static let close: NSString = ">" | |
| static let endTagIndicator: NSString = "/" | |
| static func endTag(tagName: NSString) -> NSString { | |
| // </XX> | |
| return "\(Tag.open)\(Tag.endTagIndicator)\(tagName)\(Tag.close)" | |
| } | |
| var tags: [String] { | |
| return Tag.tags(tagName as String) | |
| } | |
| var openCloseTagLength: Int { | |
| return tags.joinWithSeparator("").characters.count | |
| } | |
| static func openTag(tagName: String) -> String { return "<\(tagName)>" } | |
| static func closeTag(tagName: String) -> String { return "</\(tagName)>" } | |
| static func tags(tagName: String) -> [String] { | |
| return [Tag.openTag(tagName), Tag.closeTag(tagName)] | |
| } | |
| } | |
| // Simple flat tag parser to embed TCPA hyperlink. | |
| // - nested tag is not supported | |
| // - No speace in tag (correct: "<XX></xx>", wrong: "<XX ></ XX>") | |
| class TagParser { | |
| // entry point and dispatcher | |
| static func parse(source: NSString, pos: Int = 0) -> TagParseResult { | |
| guard pos < source.length else { return [] } | |
| guard let tagOpenRg = source.search(Tag.open, from: pos).value else { | |
| let txt = source.substring(pos, end: source.length) | |
| return [Token.Text(text: txt, pos: pos)] | |
| } | |
| let txt = source.substring(pos, end: tagOpenRg.location - 1) | |
| return [Token.Text(text: txt, pos: pos)] + TagParser.parseTag(source, pos: tagOpenRg.location) | |
| } | |
| // parse <XX>aaa</XX> | |
| private static func parseTag(source: NSString, pos: Int) -> TagParseResult { | |
| let tagNameStartPos = pos + Tag.open.length | |
| guard let tagCloseRg = source.search(Tag.close, from: pos).value where tagCloseRg.location > tagNameStartPos else { | |
| // no close ">" or no tag name "<>". just return text | |
| return [Token.Text(text: source, pos: pos)] | |
| } | |
| let tagName = source.substring(tagNameStartPos, end: tagCloseRg.location - 1) | |
| let elementStartPos = tagCloseRg.succeedingPos | |
| guard let endTagRg = source.search(Tag.endTag(tagName), from: elementStartPos).value else { | |
| // no end tag | |
| return [Token.Text(text: source, pos: pos)] | |
| } | |
| let element = source.substring(elementStartPos, end: endTagRg.location - 1) | |
| let tag = Tag(tagName: tagName, element: element) | |
| return [Token.TaggedText(tag: tag, pos: pos)] + TagParser.parse(source, pos: endTagRg.succeedingPos) | |
| } | |
| enum Token { | |
| case Text(text: NSString, pos: Int), TaggedText(tag: Tag, pos: Int) | |
| } | |
| } | |
| typealias TagParseResult = [TagParser.Token] | |
| extension NSRange { | |
| var value: NSRange? { | |
| guard self.location != NSNotFound else { | |
| return nil | |
| } | |
| return self | |
| } | |
| var succeedingPos: Int { | |
| return self.location + self.length | |
| } | |
| } | |
| extension NSString { | |
| func substring(start: Int = 0, length: Int) -> NSString { | |
| let s = max(0, start) | |
| let l = min(self.length - s, length) | |
| return self.substringWithRange(NSMakeRange(s, l)) | |
| } | |
| func substring(start: Int = 0, end: Int) -> NSString { | |
| return self.substring(start, length: (end - start + 1)) | |
| } | |
| func substring(from from: Int) -> NSString { | |
| return self.substring(from, length: self.length-from) | |
| } | |
| func range(from from: Int) -> NSRange { | |
| return NSMakeRange(from, self.length - from) | |
| } | |
| func search(string: NSString, from: Int) -> NSRange { | |
| return self.rangeOfString(string as String, options: .LiteralSearch, range: self.range(from: from)) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment