Skip to content

Commit

Permalink
Improve API, CR changes
Browse files Browse the repository at this point in the history
- Add new `newlineCharacterReplacement` parameter to
`nonLineBreaking()` to allow tweaking the newline replacement behavior.

- Create new `Character.newlines` helper to contain all `Character`s in
`CharacterSet.newlines`.

- Fix docs.
  • Loading branch information
p4checo committed Apr 6, 2021
1 parent e9f00a4 commit 9f10d49
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 9 deletions.
3 changes: 3 additions & 0 deletions Sources/Extensions/Foundation/Character.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ extension Character {
static let wordJoiner: Character = "\u{2060}"
static let emDash: Character = "\u{2013}" // —
static let enDash: Character = "\u{2014}" // –

// from `CharacterSet.newlines`
static let newlines: [Character] = ["\u{A}", "\u{B}", "\u{C}", "\u{D}", "\u{85}", "\u{2028}", "\u{2029}"]
}
26 changes: 19 additions & 7 deletions Sources/Extensions/Foundation/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,30 +116,42 @@ extension String {

/// Returns a non line breaking version of `self`. Line breaking characters occurrences are replaced with
/// corresponding non line breaking variants when existent. Otherwise, word joiner characters are attached to them
/// to make them non line breaking.
///
/// - Important: Any existing newline characters are preserved.
/// to make them non line breaking. Existing newlines can be replaced by any given string, via the optional
/// `newlineCharacterReplacement` parameter (defaults to `nil`, which preserves newlines).
///
/// The character mapping is:
/// - space (" ") -> non breaking space (`U+2028`)
/// - hyphen ("-") -> non breaking hyphen (`U+00A0`)
/// - em dash ("—") -> word joiner (`U+2060`) + em dash + word joiner (`U+2060`)
/// - em dash ("–") -> word joiner (`U+2060`) + en dash + word joiner (`U+2060`)
/// - en dash ("–") -> word joiner (`U+2060`) + en dash + word joiner (`U+2060`)
/// - question mark ("?") -> question mark + word joiner (`U+2060`)
/// - closing brace ("}") -> closing brace + word joiner (`U+2060`)
///
/// The `newlineCharacterReplacement` acts upon the characters specified in `CharacterSet.newlines`
/// (`U+000A ~ U+000D`, `U+0085`, `U+2028`, and `U+2029`), some example values are:
/// - `nil` -> newlines are preserved
/// - `""` -> newlines are stripped
/// - `String.nonBreakingSpace` -> output a single line
///
/// - Parameter newlineCharacterReplacement: The replacement string to use for newline characters (defaults to
/// `nil`).
/// - Returns: A modified version of the receiver without line breaking characters.
public func nonLineBreaking() -> String {
public func nonLineBreaking(replacingNewlinesWith newlineCharacterReplacement: String? = nil) -> String {

let newlineReplacementMap = newlineCharacterReplacement
.flatMap { replacement in Dictionary(uniqueKeysWithValues: Character.newlines.map { ($0, replacement) }) }
?? [:]

replacingOccurrencesOfCharacters(
return replacingOccurrencesOfCharacters(
in: [
" ": String.nonBreakingSpace,
"-": String.nonBreakingHyphen,
.emDash: String([.wordJoiner, .emDash, .wordJoiner]),
.enDash: String([.wordJoiner, .enDash, .wordJoiner]),
"?": "?" + .wordJoiner,
"}": "}" + .wordJoiner
],
]
.merging(newlineReplacementMap) { $1 },
skippingCharactersIn: nil
)
}
Expand Down
46 changes: 44 additions & 2 deletions Tests/AlicerceTests/Extensions/Foundation/StringTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class StringTestCase: XCTestCase {
XCTAssertEqual(original.nonLineBreaking(), expected)
}

func testNonLineBreaking_WithLineBreakingCharactersAndNewlinesInString_ShouldReturnANonLineBreakingVersion() {
func testNonLineBreaking_WithLineBreakingCharactersAndNewlinesInStringAndNilNewlineReplacement_ShouldReturnANonLineBreakingVersionAndPreserveNewlines() {

let original =
"""
Expand All @@ -170,6 +170,48 @@ class StringTestCase: XCTestCase {
\u{A}.\u{B},\u{C};\u{D}
"""

XCTAssertEqual(original.nonLineBreaking(), expected)
XCTAssertEqual(original.nonLineBreaking(replacingNewlinesWith: nil), expected)
}

func testNonLineBreaking_WithLineBreakingCharactersAndNewlinesInStringAndEmptyStringNewlineReplacement_ShouldReturnANonLineBreakingVersionAndReplaceNewlines() {

let original =
"""
\nThe quick-brown\u{85}\(String.emDash)fox\n\(String.enDash)jumps?\u{2028}\u{2029}over{the}lazy dog\n\
\u{A}.\u{B},\u{C};\u{D}
"""

let expected =
"""
The\(String.nonBreakingSpace)quick\(String.nonBreakingHyphen)brown\
\(String([.wordJoiner, .emDash, .wordJoiner]))fox\
\(String([.wordJoiner, .enDash, .wordJoiner]))jumps\
?\(String.wordJoiner)over\
{the}\(String.wordJoiner)lazy\(String.nonBreakingSpace)dog\
.,;
"""

XCTAssertEqual(original.nonLineBreaking(replacingNewlinesWith: ""), expected)
}

func testNonLineBreaking_WithLineBreakingCharactersAndNewlinesInStringAndNonNilStringNewlineReplacement_ShouldReturnANonLineBreakingVersionAndReplaceNewlines() {

let original =
"""
\nThe quick-brown\u{85}\(String.emDash)fox\n\(String.enDash)jumps?\u{2028}\u{2029}over{the}lazy dog\n\
\u{A}.\u{B},\u{C};\u{D}
"""

let expected =
"""
🦊The\(String.nonBreakingSpace)quick\(String.nonBreakingHyphen)brown🦊\
\(String([.wordJoiner, .emDash, .wordJoiner]))fox🦊\
\(String([.wordJoiner, .enDash, .wordJoiner]))jumps\
?\(String.wordJoiner)🦊🦊over\
{the}\(String.wordJoiner)lazy\(String.nonBreakingSpace)dog🦊\
🦊.🦊,🦊;🦊
"""

XCTAssertEqual(original.nonLineBreaking(replacingNewlinesWith: "🦊"), expected)
}
}

0 comments on commit 9f10d49

Please sign in to comment.