Skip to content

Commit

Permalink
Various updates:
Browse files Browse the repository at this point in the history
- handle properly standalone '&', '<', '>' in HTML parser (fixes #40)
- case insensitive tags (fixes #30)
- add support for '&nbsp;' (fixes #28)
  • Loading branch information
psharanda committed Jul 27, 2018
1 parent cc821bf commit c0d2c36
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Atributika.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Atributika"
s.version = "4.4.3"
s.version = "4.5.0"
s.summary = "Convert text with HTML tags, hashtags, mentions, links into NSAttributedString. Make them clickable with UILabel drop-in replacement."
s.description = <<-DESC
`Atributika` is an easy and painless way to build NSAttributedString. It is able to detect HTML-like tags, links, phone numbers, hashtags, any regex or even standard ios data detectors and style them with various attributes like font, color, etc. `Atributika` comes with drop-in label replacement `AttributedLabel` which is able to make any detection clickable.
Expand Down
23 changes: 18 additions & 5 deletions Demo/Snippet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ func stringWithBoldItalicUnderline() -> NSAttributedString {
}

func stringWithImage() -> NSAttributedString {

let str = "Running with <img id=\"scissors\"></img>!".style(tags: [])
let font = UIFont(name: "HelveticaNeue-BoldItalic", size: 12)!
let uib = Style("b").font(font).underlineStyle(.styleSingle)
let str = "<b>Running</b> with <img id=\"scissors\"></img>!".style(tags: uib)

let mutableAttrStr = NSMutableAttributedString(attributedString: str.attributedString)

Expand All @@ -168,11 +169,22 @@ func stringWithImage() -> NSAttributedString {
break
}
}



return mutableAttrStr
}

func stringWithStrikethrough() -> NSAttributedString {
let all = Style.font(.systemFont(ofSize: 20))
let strike = Style("strike").strikethroughStyle(.styleSingle).strikethroughColor(.black)
let code = Style("code").foregroundColor(.red)

let str = "<code>my code</code> <strike>test</strike> testing"
.style(tags: [strike,code])
.styleAll(all)
.attributedString
return str
}

func allSnippets() -> [NSAttributedString] {
return [
stringWithAtributikaLogo(),
Expand All @@ -188,7 +200,8 @@ func allSnippets() -> [NSAttributedString] {
stringWithUnorderedList(),
stringWithHref(),
stringWithBoldItalicUnderline(),
stringWithImage()
stringWithImage(),
stringWithStrikethrough()
]
}

2 changes: 1 addition & 1 deletion Sources/AttributedText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ extension String: AttributedTextProtocol {

tagsInfo.forEach { t in

if let style = (tags.first { style in style.name == t.tag.name }) {
if let style = (tags.first { style in style.name.lowercased() == t.tag.name.lowercased() }) {
ds.append(Detection(type: .tag(t.tag), style: style, range: t.range))
} else {
ds.append(Detection(type: .tag(t.tag), style: Style(), range: t.range))
Expand Down
76 changes: 49 additions & 27 deletions Sources/String+Detection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ extension String {
"amp":"&",
"apos":"'",
"lt":"<",
"gt":">"]
"gt":">",
"nbsp":" "] //just replacing it with space, not going to implement non-breaking logic

public func detectTags(transformers: [TagTransformer] = []) -> (string: String, tagsInfo: [TagInfo]) {

Expand All @@ -104,39 +105,60 @@ extension String {
resultString += textString
} else {
if scanner.scanString("<") != nil {
let tagType = scanner.scanString("/") == nil ? TagType.start : TagType.end
if let tagString = scanner.scanUpTo(">") {

if let tag = parseTag(tagString, parseAttributes: tagType == .start ) {

let resultTextEndIndex = resultString.endIndex

if let transformer = transformers.first(where: {
$0.tagName == tag.name && $0.tagType == tagType
}) {
resultString += transformer.transform(tag)
}

if tagType == .start {
tagsStack.append((tag, resultTextEndIndex))
} else {
for (index, (tagInStack, startIndex)) in tagsStack.enumerated().reversed() {
if tagInStack.name == tag.name {
tagsResult.append(TagInfo(tag: tagInStack, range: startIndex..<resultTextEndIndex))
tagsStack.remove(at: index)
break

if scanner.isAtEnd {
resultString += "<"
} else {
let nextChar = (scanner.string as NSString).substring(with: NSRange(location: scanner.scanLocation, length: 1))
if CharacterSet.letters.contains(nextChar.unicodeScalars.first!) || (nextChar == "/") {
let tagType = scanner.scanString("/") == nil ? TagType.start : TagType.end
if let tagString = scanner.scanUpTo(">") {

if scanner.scanString(">") != nil {
if let tag = parseTag(tagString, parseAttributes: tagType == .start ) {

let resultTextEndIndex = resultString.endIndex

if let transformer = transformers.first(where: {
$0.tagName.lowercased() == tag.name.lowercased() && $0.tagType == tagType
}) {
resultString += transformer.transform(tag)
}

if tagType == .start {
tagsStack.append((tag, resultTextEndIndex))
} else {
for (index, (tagInStack, startIndex)) in tagsStack.enumerated().reversed() {
if tagInStack.name.lowercased() == tag.name.lowercased() {
tagsResult.append(TagInfo(tag: tagInStack, range: startIndex..<resultTextEndIndex))
tagsStack.remove(at: index)
break
}
}
}
}
} else {
resultString += "<"
resultString += tagString
}
}
} else {
resultString += "<"
}
scanner.scanString(">")
}
} else if scanner.scanString("&") != nil {
if let specialString = scanner.scanUpTo(";") {
if let spec = String.specials[specialString] {
resultString += spec

var foundSpecial = false
for (k, v) in String.specials {
if scanner.scanString(k + ";") != nil {
resultString += v
foundSpecial = true
break
}
scanner.scanString(";")
}

if !foundSpecial {
resultString += "&"
}
}
}
Expand Down
54 changes: 53 additions & 1 deletion Tests/AtributikaTests/AtributikaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class AtributikaTests: XCTestCase {

let test = "Hello <World!!!".style(tags: Style("b").font(.boldSystemFont(ofSize: 45))).attributedString

let reference = NSMutableAttributedString(string: "Hello ")
let reference = NSMutableAttributedString(string: "Hello <World!!!")

XCTAssertEqual(test, reference)
}
Expand Down Expand Up @@ -430,6 +430,58 @@ class AtributikaTests: XCTestCase {
XCTAssertEqual(tags[0].tag.attributes["target"], "")
XCTAssertEqual(tags[0].tag.attributes["href"], "http://foo.com")
}

func testSpecials() {
XCTAssertEqual("Hello&amp;World!!!".detectTags().string , "Hello&World!!!")
XCTAssertEqual("Hello&".detectTags().string , "Hello&")
XCTAssertEqual("Hello&World".detectTags().string , "Hello&World")
XCTAssertEqual("&quot;Quote&quot;".detectTags().string , "\"Quote\"")
XCTAssertEqual("&".detectTags().string , "&")
XCTAssertEqual("&&amp;".detectTags().string , "&&")
XCTAssertEqual("4>5".detectTags().string , "4>5")
XCTAssertEqual("4<5".detectTags().string , "4<5")
XCTAssertEqual("<".detectTags().string , "<")
XCTAssertEqual(">".detectTags().string , ">")
XCTAssertEqual("<a".detectTags().string , "<a")
XCTAssertEqual("<a>".detectTags().string , "")
XCTAssertEqual("< a>".detectTags().string , "< a>")
}

func testCaseInsensitive1() {

let test = "<B>Hello World</B>!!!".style(tags: Style("b").font(.boldSystemFont(ofSize: 45)))
.styleAll(.font(.systemFont(ofSize: 12)))
.attributedString

let reference = NSMutableAttributedString(string: "Hello World!!!")
reference.addAttributes([NSAttributedStringKey.font: Font.boldSystemFont(ofSize: 45)], range: NSMakeRange(0, 11))
reference.addAttributes([NSAttributedStringKey.font: Font.systemFont(ofSize: 12)], range: NSMakeRange(11, 3))

XCTAssertEqual(test,reference)

}

func testCaseInsensitive2() {

let test = "<B>Hello World</b>!!!".style(tags: Style("B").font(.boldSystemFont(ofSize: 45)))
.styleAll(.font(.systemFont(ofSize: 12)))
.attributedString

let reference = NSMutableAttributedString(string: "Hello World!!!")
reference.addAttributes([NSAttributedStringKey.font: Font.boldSystemFont(ofSize: 45)], range: NSMakeRange(0, 11))
reference.addAttributes([NSAttributedStringKey.font: Font.systemFont(ofSize: 12)], range: NSMakeRange(11, 3))

XCTAssertEqual(test,reference)

}

func testCaseInsensitiveBr() {
let test = "Hello<BR>World!!!".style(tags: []).attributedString

let reference = NSMutableAttributedString(string: "Hello\nWorld!!!")

XCTAssertEqual(test, reference)
}
}


Expand Down

0 comments on commit c0d2c36

Please sign in to comment.