Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
34 Add error handling (#42)
Browse files Browse the repository at this point in the history
* Add error handling

* tests

* PR comments

* More pr comments

* pr comment
  • Loading branch information
OrlaM committed Nov 21, 2018
1 parent b36d5d3 commit fb36de1
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 38 deletions.
4 changes: 4 additions & 0 deletions RichTextView.xcodeproj/project.pbxproj
Expand Up @@ -10,6 +10,7 @@
04E795A425A65D81DCE2EB46 /* Pods_RichTextViewUnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3D7221CDEA76C83413DAA33 /* Pods_RichTextViewUnitTests.framework */; };
22821FA0122B40D993544497 /* Pods_RichTextView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C012345B5710779C8C7E7E7A /* Pods_RichTextView.framework */; };
2AD49AFD21A31A7400173AE1 /* RichTextViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781488FC2194E71C00DED7A1 /* RichTextViewSpec.swift */; };
2AE9362D21A5B97E0057AE15 /* ParsingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE9362C21A5B97E0057AE15 /* ParsingError.swift */; };
2FA42ECC0C4D26E4CCB5216B /* Pods_RichTextViewUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 18DA45F3356CF26110455FF8 /* Pods_RichTextViewUITests.framework */; };
781488EE2194E6B600DED7A1 /* RichTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 781488EC2194E6B600DED7A1 /* RichTextView.h */; settings = {ATTRIBUTES = (Public, ); }; };
781488F52194E6D600DED7A1 /* RichTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781488F42194E6D600DED7A1 /* RichTextView.swift */; };
Expand Down Expand Up @@ -51,6 +52,7 @@
/* Begin PBXFileReference section */
07FC2CA5E35A2B6DC841428F /* Pods-RichTextViewUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichTextViewUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RichTextViewUITests/Pods-RichTextViewUITests.release.xcconfig"; sourceTree = "<group>"; };
18DA45F3356CF26110455FF8 /* Pods_RichTextViewUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RichTextViewUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2AE9362C21A5B97E0057AE15 /* ParsingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsingError.swift; sourceTree = "<group>"; };
65B42AAD2EB3295380AB828B /* Pods-RichTextViewUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichTextViewUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RichTextViewUITests/Pods-RichTextViewUITests.debug.xcconfig"; sourceTree = "<group>"; };
6B539959045BD559E2435740 /* Pods-RichTextViewUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichTextViewUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RichTextViewUnitTests/Pods-RichTextViewUnitTests.debug.xcconfig"; sourceTree = "<group>"; };
781488E92194E6B600DED7A1 /* RichTextView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RichTextView.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -210,6 +212,7 @@
children = (
78D1DC3421A47B23004CA0F1 /* RichDataType.swift */,
7886C3B621A3541D00C11817 /* RichTextViewConstants.swift */,
2AE9362C21A5B97E0057AE15 /* ParsingError.swift */,
);
path = Constants;
sourceTree = "<group>";
Expand Down Expand Up @@ -520,6 +523,7 @@
7886C3AB21A319E000C11817 /* LatexParserProtocol.swift in Sources */,
78D1DC3521A47B23004CA0F1 /* RichDataType.swift in Sources */,
78148940219536DF00DED7A1 /* RichTextParser.swift in Sources */,
2AE9362D21A5B97E0057AE15 /* ParsingError.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
24 changes: 24 additions & 0 deletions Source/Constants/ParsingError.swift
@@ -0,0 +1,24 @@
//
// ParsingError.swift
// RichTextView
//
// Created by Orla Mitchell on 2018-11-21.
// Copyright © 2018 Top Hat. All rights reserved.
//

public enum ParsingError: LocalizedError {
case attributedTextGeneration(text: String)
case webViewGeneration(link: String)
case latexGeneration(text: String)

public var errorDescription: String? {
switch self {
case let .attributedTextGeneration(text):
return "Error Generating Attributed Text: " + text
case let .webViewGeneration(link):
return "Error Generating Webview: " + link
case let .latexGeneration(text):
return "Error Generating Latex: " + text
}
}
}
4 changes: 2 additions & 2 deletions Source/Constants/RichDataType.swift
Expand Up @@ -7,6 +7,6 @@
//

enum RichDataType {
case video(tag: String)
case text(richText: NSAttributedString, font: UIFont)
case video(tag: String, error: ParsingError?)
case text(richText: NSAttributedString, font: UIFont, errors: [ParsingError]?)
}
36 changes: 32 additions & 4 deletions Source/RichTextView.swift
Expand Up @@ -16,18 +16,22 @@ public class RichTextView: UIView {
private let richTextParser: RichTextParser
private let textColor: UIColor

public var errors: [ParsingError]?

// MARK: - Init

public init(input: String = "",
latexParser: LatexParserProtocol = LatexParser(),
font: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize),
textColor: UIColor = UIColor.black,
frame: CGRect) {
frame: CGRect,
completion: (([ParsingError]?) -> ())? = nil) {
self.input = input
self.richTextParser = RichTextParser(latexParser: latexParser, font: font)
self.textColor = textColor
super.init(frame: frame)
self.setupSubviews()
completion?(errors)
}

required init?(coder aDecoder: NSCoder) {
Expand Down Expand Up @@ -62,11 +66,35 @@ public class RichTextView: UIView {
func generateViews(from input: String) -> [UIView] {
return self.richTextParser.getRichDataTypes(from: input).compactMap { (richDataType: RichDataType) -> UIView? in
switch richDataType {
case .video(let tag):
return RichWebViewGenerator.getWebView(from: tag)
case .text(let richText, let font):
case .video(let tag, let error):
self.appendError(error)
let webView = RichWebViewGenerator.getWebView(from: tag)
if webView == nil {
self.appendError(ParsingError.webViewGeneration(link: tag))
}
return webView
case .text(let richText, let font, let errors):
self.appendErrors(errors)
return RichLabelGenerator.getLabel(from: richText, font: font, textColor: textColor)
}
}
}

private func appendErrors(_ errors: [ParsingError]?) {
if let errors = errors {
if self.errors == nil {
self.errors = [ParsingError]()
}
self.errors?.append(contentsOf: errors)
}
}

private func appendError(_ error: ParsingError?) {
if let error = error {
if self.errors == nil {
self.errors = [ParsingError]()
}
self.errors?.append(error)
}
}
}
6 changes: 3 additions & 3 deletions Source/Text Parsing/LatexParserProtocol.swift
Expand Up @@ -10,11 +10,11 @@ import Down
import iosMath

public protocol LatexParserProtocol: class {
func extractLatex(from input: String) -> NSAttributedString
func extractLatex(from input: String) -> NSAttributedString?
}

extension LatexParserProtocol {
public func extractLatex(from input: String) -> NSAttributedString {
public func extractLatex(from input: String) -> NSAttributedString? {

let latexInput = self.extractLatexStringInsideTags(from: input)

Expand All @@ -26,7 +26,7 @@ extension LatexParserProtocol {
label.frame = newFrame

guard let image = self.image(from: label) else {
return NSAttributedString(string: latexInput)
return nil
}

let textAttachment = NSTextAttachment()
Expand Down
51 changes: 37 additions & 14 deletions Source/Text Parsing/RichTextParser.swift
Expand Up @@ -13,6 +13,7 @@ class RichTextParser {
private enum ParserConstants {
static let latexRegex = "\\[math\\](.*?)\\[\\/math\\]"
}

// MARK: - Dependencies

let latexParser: LatexParserProtocol
Expand All @@ -28,48 +29,70 @@ class RichTextParser {
// MARK: - Utility Functions

func getRichDataTypes(from input: String) -> [RichDataType] {
var errors: [ParsingError]?
return self.splitInputOnVideoPortions(input).compactMap { input -> RichDataType in
if self.isStringAVideoTag(input) {
return RichDataType.video(tag: input)
return RichDataType.video(tag: input, error: nil)
}
let results = self.richTextToAttributedString(from: input)
if errors == nil {
errors = results.errors
} else if let resultErrors = results.errors {
errors?.append(contentsOf: resultErrors)
}
return RichDataType.text(richText: self.richTextToAttributedString(from: input), font: self.font)
return RichDataType.text(richText: results.output, font: self.font, errors: errors)
}
}

// MARK: - Helpers

func richTextToAttributedString(from input: String) -> NSAttributedString {
func richTextToAttributedString(from input: String) -> (output: NSAttributedString, errors: [ParsingError]?) {
let components = self.seperateComponents(from: input)
let attributedArray = self.generateAttributedStringArray(from: components)
let results = self.generateAttributedStringArray(from: components)
let attributedArray = results.output
let mutableAttributedString = NSMutableAttributedString()
for attributedString in attributedArray {
mutableAttributedString.append(attributedString)
}
return mutableAttributedString
return (mutableAttributedString, results.errors)
}

func generateAttributedStringArray(from input: [String]) -> [NSAttributedString] {
func generateAttributedStringArray(from input: [String]) -> (output: [NSAttributedString], errors: [ParsingError]?) {
var output = [NSAttributedString]()
var errors: [ParsingError]?
for element in input {
if let attributedString = self.getAttributedText(from: element) {
let result = self.getAttributedText(from: element)
if let attributedString = result.output {
output.append(attributedString)
} else {
// TODO: #34 Add error handling
var tempErrors = [ParsingError]()
if let error = result.error {
tempErrors.append(error)
}
if errors == nil {
errors = tempErrors
} else {
errors?.append(contentsOf: tempErrors)
}
}
}
return output
return (output, errors)
}

private func getAttributedText(from input: String) -> NSAttributedString? {
private func getAttributedText(from input: String) -> (output: NSAttributedString?, error: ParsingError?) {
if isTextLatex(input) {
return self.extractLatex(from: input)
let latex = self.extractLatex(from: input)
if latex == nil {
return (NSAttributedString(string: input), ParsingError.latexGeneration(text: input))
}
return (latex, nil)
}
guard let attributedInput = try? Down(markdownString: self.stripCodeTagsIfNecessary(from: input)).toAttributedString() else {
return nil
return (nil, ParsingError.attributedTextGeneration(text: input))
}
let mutableAttributedInput = NSMutableAttributedString(attributedString: attributedInput)
mutableAttributedInput.replaceFont(with: self.font)
return mutableAttributedInput
return (mutableAttributedInput, nil)
}

func seperateComponents(from input: String) -> [String] {
Expand All @@ -82,7 +105,7 @@ class RichTextParser {
)
}

func extractLatex(from input: String) -> NSAttributedString {
func extractLatex(from input: String) -> NSAttributedString? {
return self.latexParser.extractLatex(from: input)
}

Expand Down
34 changes: 19 additions & 15 deletions UnitTests/Text Parsing/RichTextParserSpec.swift
Expand Up @@ -34,7 +34,7 @@ class RichTextParserSpec: QuickSpec {
context("Latex Parsing") {
it("succesfully returns an NSAttributedString with an image") {
let output = self.richTextParser.extractLatex(from: MarkDownText.basicLatex)
self.testAttributedStringContainsImage(output)
self.testAttributedStringContainsImage(output!)
}
}
context("Breaking up text into componenets") {
Expand All @@ -48,7 +48,8 @@ class RichTextParserSpec: QuickSpec {
}
it("generates an attributed string array with the correct components for latex and markdown") {
let components = self.richTextParser.seperateComponents(from: MarkDownText.complexLatex)
let attributedStrings = self.richTextParser.generateAttributedStringArray(from: components)
let results = self.richTextParser.generateAttributedStringArray(from: components)
let attributedStrings = results.output

expect(attributedStrings.count).to(equal(3))
expect(attributedStrings[1].string).to(equal("More Text\n"))
Expand All @@ -57,21 +58,22 @@ class RichTextParserSpec: QuickSpec {
}
it("generates an attributed string array with the correct components for html") {
let components = self.richTextParser.seperateComponents(from: MarkDownText.complexHTML)
let attributedStrings = self.richTextParser.generateAttributedStringArray(from: components)
let results = self.richTextParser.generateAttributedStringArray(from: components)
let attributedStrings = results.output

expect(attributedStrings.count).to(equal(1))
expect(attributedStrings[0].string).to(equal("\nMessage\n\n"))
}
}
context("Rich text to attributed string") {
it("generates a single attributed string with multiple rich text types") {
let regularText = self.richTextParser.richTextToAttributedString(from: MarkDownText.regularText)
let regularText = self.richTextParser.richTextToAttributedString(from: MarkDownText.regularText).output
expect(regularText.string.range(of: "Some Text")).toNot(beNil())

let complexHTML = self.richTextParser.richTextToAttributedString(from: MarkDownText.complexHTML)
let complexHTML = self.richTextParser.richTextToAttributedString(from: MarkDownText.complexHTML).output
expect(complexHTML.string.range(of: "Message")).toNot(beNil())

let complexLatex = self.richTextParser.richTextToAttributedString(from: MarkDownText.complexLatex)
let complexLatex = self.richTextParser.richTextToAttributedString(from: MarkDownText.complexLatex).output
expect(complexLatex.string.range(of: "More Text")).toNot(beNil())
}
}
Expand All @@ -80,29 +82,31 @@ class RichTextParserSpec: QuickSpec {
let output = self.richTextParser.getRichDataTypes(from: "Look at this video: youtube[12345]")
expect(output.count).to(equal(2))
expect(output[0]).to(equal(RichDataType.text(
richText: self.richTextParser.richTextToAttributedString(from: "Look at this video: "),
font: self.richTextParser.font
richText: self.richTextParser.richTextToAttributedString(from: "Look at this video: ").output,
font: self.richTextParser.font,
errors: [ParsingError]()
)))
expect(output[1]).to(equal(RichDataType.video(tag: "youtube[12345]")))
expect(output[1]).to(equal(RichDataType.video(tag: "youtube[12345]", error: nil)))
}
it("properly generates the correct views with only non-video strings") {
let output = self.richTextParser.getRichDataTypes(from: "Look at this!")
expect(output.count).to(equal(1))
expect(output[0]).to(equal(RichDataType.text(
richText: self.richTextParser.richTextToAttributedString(from: "Look at this!"),
font: self.richTextParser.font
richText: self.richTextParser.richTextToAttributedString(from: "Look at this!").output,
font: self.richTextParser.font,
errors: [ParsingError]()
)))
}
it("properly generates the correct views with only video strings") {
let output = self.richTextParser.getRichDataTypes(from: "youtube[12345]")
expect(output.count).to(equal(1))
expect(output[0]).to(equal(RichDataType.video(tag: "youtube[12345]")))
expect(output[0]).to(equal(RichDataType.video(tag: "youtube[12345]", error: nil)))
}

}
context("Strip Code Tags") {
it("Successfully strips code tags from input") {
let output = self.richTextParser.richTextToAttributedString(from: MarkDownText.codeText)
let output = self.richTextParser.richTextToAttributedString(from: MarkDownText.codeText).output
expect(output.string).to(equal("print('Hello World')\n"))
}
}
Expand Down Expand Up @@ -149,9 +153,9 @@ extension RichDataType: Equatable {
public static func == (lhs: RichDataType, rhs: RichDataType) -> Bool {
switch (lhs, rhs) {
case let (.text(a), .text(b)):
return a == b
return a.richText == b.richText
case let (.video(a), .video(b)):
return a == b
return a.tag == b.tag
default:
return false
}
Expand Down

0 comments on commit fb36de1

Please sign in to comment.