Skip to content

Instantly share code, notes, and snippets.

@hiroshi-maybe
Created December 24, 2015 10:01
Show Gist options
  • Select an option

  • Save hiroshi-maybe/cb2989c2e704ec71de6c to your computer and use it in GitHub Desktop.

Select an option

Save hiroshi-maybe/cb2989c2e704ec71de6c to your computer and use it in GitHub Desktop.
Simple flat tag parser
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