Skip to content

Commit

Permalink
Merge pull request #275 from Raizlabs/feature/26-string-transforms
Browse files Browse the repository at this point in the history
String transforms
  • Loading branch information
ZevEisenberg authored Mar 27, 2017
2 parents 35317b7 + e4897dc commit 79a2c0b
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 30 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ disabled_rules: # rule identifiers to exclude from running
- line_length
- nesting
- variable_name
- large_tuple

statement_position:
statement_mode: uncuddled_else
Expand Down
20 changes: 20 additions & 0 deletions BonMot.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@
CD3EEE731DCA6BCB002C41BB /* FontInspectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3EEE721DCA6BCB002C41BB /* FontInspectorTests.swift */; };
CD3EEE741DCA6BCB002C41BB /* FontInspectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3EEE721DCA6BCB002C41BB /* FontInspectorTests.swift */; };
CD3EEE751DCA6BCB002C41BB /* FontInspectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD3EEE721DCA6BCB002C41BB /* FontInspectorTests.swift */; };
CD6BE30A1E8590F900FB308F /* Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE3081E8590E900FB308F /* Transform.swift */; };
CD6BE30B1E8590FA00FB308F /* Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE3081E8590E900FB308F /* Transform.swift */; };
CD6BE30C1E8590FA00FB308F /* Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE3081E8590E900FB308F /* Transform.swift */; };
CD6BE30D1E8590FB00FB308F /* Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE3081E8590E900FB308F /* Transform.swift */; };
CD6BE30F1E85924800FB308F /* TransformTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE30E1E85924800FB308F /* TransformTests.swift */; };
CD6BE3101E85924800FB308F /* TransformTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE30E1E85924800FB308F /* TransformTests.swift */; };
CD6BE3111E85924800FB308F /* TransformTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE30E1E85924800FB308F /* TransformTests.swift */; };
CD6BE3121E85924800FB308F /* TransformTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6BE30E1E85924800FB308F /* TransformTests.swift */; };
CD70C54E1DCD22A7003C063A /* StylisticAlternates.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD70C54C1DCD22A1003C063A /* StylisticAlternates.swift */; };
CD70C54F1DCD22A8003C063A /* StylisticAlternates.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD70C54C1DCD22A1003C063A /* StylisticAlternates.swift */; };
CD70C5501DCD22A8003C063A /* StylisticAlternates.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD70C54C1DCD22A1003C063A /* StylisticAlternates.swift */; };
Expand Down Expand Up @@ -322,6 +330,8 @@
CD3EEE661DC92CF0002C41BB /* Ligatures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Ligatures.swift; sourceTree = "<group>"; };
CD3EEE6C1DCA68E1002C41BB /* FontInspector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontInspector.swift; sourceTree = "<group>"; };
CD3EEE721DCA6BCB002C41BB /* FontInspectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontInspectorTests.swift; sourceTree = "<group>"; };
CD6BE3081E8590E900FB308F /* Transform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transform.swift; sourceTree = "<group>"; };
CD6BE30E1E85924800FB308F /* TransformTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformTests.swift; sourceTree = "<group>"; };
CD70C54C1DCD22A1003C063A /* StylisticAlternates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StylisticAlternates.swift; sourceTree = "<group>"; };
CDCF0F1E1DCE5B9B00727AAE /* ContextualAlternates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextualAlternates.swift; sourceTree = "<group>"; };
CDEA84301D9EEC390099BD73 /* Image+Tinting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Image+Tinting.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -416,6 +426,7 @@
CD3EEE661DC92CF0002C41BB /* Ligatures.swift */,
CD70C54C1DCD22A1003C063A /* StylisticAlternates.swift */,
CDCF0F1E1DCE5B9B00727AAE /* ContextualAlternates.swift */,
CD6BE3081E8590E900FB308F /* Transform.swift */,
);
name = Style;
sourceTree = "<group>";
Expand Down Expand Up @@ -574,6 +585,7 @@
CDEA84351D9EEC490099BD73 /* ImageTintingTests.swift */,
1E902AB31DA6915F00BD154D /* TextAlignmentConstraintTests.swift */,
CD3EEE721DCA6BCB002C41BB /* FontInspectorTests.swift */,
CD6BE30E1E85924800FB308F /* TransformTests.swift */,
AB167C8F1D997A2C0084808D /* Info.plist */,
);
path = Tests;
Expand Down Expand Up @@ -1118,6 +1130,7 @@
ABA75DD91D99E10400B64705 /* NamedStyles.swift in Sources */,
CDEA84341D9EEC390099BD73 /* Image+Tinting.swift in Sources */,
ABA75DDB1D99E10400B64705 /* StringStyle+Part.swift in Sources */,
CD6BE30D1E8590FB00FB308F /* Transform.swift in Sources */,
ABA75DDD1D99E10400B64705 /* XMLBuilder.swift in Sources */,
ABA75DDE1D99E10400B64705 /* NSAttributedString+BonMot.swift in Sources */,
CD3EEE711DCA68FB002C41BB /* FontInspector.swift in Sources */,
Expand All @@ -1139,6 +1152,7 @@
ABA75DF31D99E10F00B64705 /* XMLTagStyleBuilderTests.swift in Sources */,
ABA75DF41D99E10F00B64705 /* NSAttributedStringDebugTests.swift in Sources */,
ABA75DF51D99E10F00B64705 /* AssertHelpers.swift in Sources */,
CD6BE3121E85924800FB308F /* TransformTests.swift in Sources */,
ABA75DF61D99E10F00B64705 /* BONFontBehaviorTests.swift in Sources */,
ABA75DF71D99E10F00B64705 /* ComposableTests.swift in Sources */,
ABA75DF81D99E10F00B64705 /* UIKitBonMotTests.swift in Sources */,
Expand Down Expand Up @@ -1186,6 +1200,7 @@
ABCD3D811D96F02F00273936 /* XMLBuilder.swift in Sources */,
ABCD3D701D96F02F00273936 /* NSAttributedString+BonMot.swift in Sources */,
AB560A061D9F692000100D15 /* AttributedStringTransformation.swift in Sources */,
CD6BE30A1E8590F900FB308F /* Transform.swift in Sources */,
AB30D51D1D9D95F5006ADC9D /* EmbeddedTransformation.swift in Sources */,
ABCD3D6E1D96F02F00273936 /* Compatibility.swift in Sources */,
ABCD3D7C1D96F02F00273936 /* NSAttributedString+Adaptive.swift in Sources */,
Expand All @@ -1203,6 +1218,7 @@
ABCD3DB31D96F06800273936 /* Compatibility+Tests.swift in Sources */,
ABCD3DBB1D96F06800273936 /* UIKitBehaviorTests.swift in Sources */,
CD3EEE731DCA6BCB002C41BB /* FontInspectorTests.swift in Sources */,
CD6BE30F1E85924800FB308F /* TransformTests.swift in Sources */,
ABCD3DB01D96F06800273936 /* AdaptiveStyleTests.swift in Sources */,
ABCD3DBD1D96F06800273936 /* XMLTagStyleBuilderTests.swift in Sources */,
ABCD3DB61D96F06800273936 /* NSAttributedStringDebugTests.swift in Sources */,
Expand Down Expand Up @@ -1244,6 +1260,7 @@
ABCD3DDB1D96F6A500273936 /* UIKit+Helpers.swift in Sources */,
ABCD3DDC1D96F6A500273936 /* XMLBuilder.swift in Sources */,
AB8497B01DA16DAE00FE3414 /* NSAttributedString+Adaptive.swift in Sources */,
CD6BE30B1E8590FA00FB308F /* Transform.swift in Sources */,
ABCD3DDD1D96F6A500273936 /* NSAttributedString+BonMot.swift in Sources */,
ABCD3DDE1D96F6A500273936 /* Compatibility.swift in Sources */,
AB8497B41DA16DAE00FE3414 /* AdaptiveStyle.swift in Sources */,
Expand All @@ -1261,6 +1278,7 @@
ABCD3DF21D96F6E200273936 /* Compatibility+Tests.swift in Sources */,
ABCD3DF31D96F6E200273936 /* UIKitBehaviorTests.swift in Sources */,
CD3EEE741DCA6BCB002C41BB /* FontInspectorTests.swift in Sources */,
CD6BE3101E85924800FB308F /* TransformTests.swift in Sources */,
ABCD3DF41D96F6E200273936 /* AdaptiveStyleTests.swift in Sources */,
ABCD3DF51D96F6E200273936 /* XMLTagStyleBuilderTests.swift in Sources */,
ABCD3DF61D96F6E200273936 /* NSAttributedStringDebugTests.swift in Sources */,
Expand All @@ -1281,6 +1299,7 @@
files = (
AB30D5111D9D9438006ADC9D /* Composable.swift in Sources */,
ABA75E0E1D99F9D500B64705 /* Tracking.swift in Sources */,
CD6BE30C1E8590FA00FB308F /* Transform.swift in Sources */,
1E7B10EE1DA41F9300C668A4 /* TextAlignmentConstraint.swift in Sources */,
CDCF0F211DCE5B9B00727AAE /* ContextualAlternates.swift in Sources */,
ABCD3E0D1D980E4900273936 /* Platform.swift in Sources */,
Expand Down Expand Up @@ -1311,6 +1330,7 @@
AB6633091DA5C5F90007F301 /* BONFontBehaviorTests.swift in Sources */,
ABCD3E301D980E5500273936 /* AssertHelpers.swift in Sources */,
CD3EEE751DCA6BCB002C41BB /* FontInspectorTests.swift in Sources */,
CD6BE3111E85924800FB308F /* TransformTests.swift in Sources */,
ABCD3E321D980E5500273936 /* ComposableTests.swift in Sources */,
CDEA84381D9EEC490099BD73 /* ImageTintingTests.swift in Sources */,
1E902AB61DA6915F00BD154D /* TextAlignmentConstraintTests.swift in Sources */,
Expand Down
41 changes: 40 additions & 1 deletion Sources/Compatibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
// Declared as public because XMLBuilderError needs it.
public typealias Error = ErrorType

// Declared as public because Transform needs it
public typealias Locale = NSLocale

typealias XMLParser = NSXMLParser
typealias XMLParserDelegate = NSXMLParserDelegate
typealias CharacterSet = NSCharacterSet
Expand Down Expand Up @@ -272,10 +275,38 @@
return componentsSeparatedByCharactersInSet(separator)
}

func appending(string: String) -> String {
return stringByAppendingString(string)
}

init<T>(describing instance: T) {
self.init(instance)
}

var localizedLowercase: String {
return localizedLowercaseString
}

var localizedUppercase: String {
return localizedUppercaseString
}

var localizedCapitalized: String {
return localizedCapitalizedString
}

func lowercased(with locale: Locale) -> String {
return self.lowercaseStringWithLocale(locale)
}

func uppercased(with locale: Locale) -> String {
return self.uppercaseStringWithLocale(locale)
}

func capitalized(with locale: Locale) -> String {
return self.capitalizedStringWithLocale(locale)
}

}

extension NSObjectProtocol {
Expand All @@ -289,6 +320,14 @@
}

}

extension Locale {

convenience init(identifier: String) {
self.init(localeIdentifier: identifier)
}

}
#endif

// MARK: - Shared (AppKit + UIKit)
Expand Down Expand Up @@ -320,7 +359,7 @@
enumerateAttribute(attrName, inRange: enumerationRange, options: opts, usingBlock: block)
}

@nonobjc final func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedStringEnumerationOptions, usingBlock block: ([String: AnyObject], NSRange, UnsafeMutablePointer<ObjCBool>) -> Void) {
@nonobjc final func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedStringEnumerationOptions, using block: ([String: AnyObject], NSRange, UnsafeMutablePointer<ObjCBool>) -> Void) {
enumerateAttributesInRange(enumerationRange, options: opts, usingBlock: block)
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/StringStyle+Part.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ extension StringStyle {
case xml
case xmlRules([XMLStyleRule])
case xmlStyler(XMLStyler)
case transform(Transform)
#if os(iOS) || os(tvOS) || os(OSX)
case fontFeature(FontFeatureProvider)

Expand Down Expand Up @@ -219,6 +220,8 @@ extension StringStyle {
self.xmlStyler = XMLStyleRule.Styler(rules: rules)
case let .xmlStyler(xmlStyler):
self.xmlStyler = xmlStyler
case let .transform(transform):
self.transform = transform
case let .style(style):
self.add(stringStyle: style)
default:
Expand Down
18 changes: 17 additions & 1 deletion Sources/StringStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public struct StringStyle {
#endif
public var tracking: Tracking?
public var xmlStyler: XMLStyler?
public var transform: Transform?

}

Expand Down Expand Up @@ -173,7 +174,21 @@ extension StringStyle {
return attributedString
}
}
return NSAttributedString(string: theString, attributes: supplyDefaults(for: existingAttributes))
let tagsApplied = NSAttributedString(string: theString, attributes: supplyDefaults(for: existingAttributes))

guard let transform = transform else {
return tagsApplied
}

let mutable = tagsApplied.mutableStringCopy()
let fullRange = NSRange(location: 0, length: mutable.length)
mutable.enumerateAttributes(in: fullRange, options: [], using: { (_, range, _) in
let substring = mutable.attributedSubstring(from: range).string
let transformed = transform.transformer(substring)
mutable.replaceCharacters(in: range, with: transformed)
})

return mutable
}

}
Expand Down Expand Up @@ -251,6 +266,7 @@ extension StringStyle {
#endif
tracking = theStringStyle.tracking ?? tracking
xmlStyler = theStringStyle.xmlStyler ?? xmlStyler
transform = theStringStyle.transform ?? transform
}

public func byAdding(stringStyle style: StringStyle) -> StringStyle {
Expand Down
42 changes: 42 additions & 0 deletions Sources/Transform.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// Transform.swift
// BonMot
//
// Created by Zev Eisenberg on 3/24/17.
// Copyright © 2017 Raizlabs. All rights reserved.
//

#if os(OSX)
import AppKit
#else
import UIKit
#endif

public enum Transform {

public typealias TransformFunction = (String) -> String

case lowercase
case uppercase
case capitalized

case lowercaseWithLocale(Locale)
case uppercaseWithLocale(Locale)
case capitalizedWithLocale(Locale)
case custom(TransformFunction)

var transformer: TransformFunction {
switch self {
case .lowercase: return { string in string.localizedLowercase }
case .uppercase: return { string in string.localizedUppercase }
case .capitalized: return { string in string.localizedCapitalized }

case .lowercaseWithLocale(let locale): return { string in string.lowercased(with: locale) }
case .uppercaseWithLocale(let locale): return { string in string.uppercased(with: locale) }
case .capitalizedWithLocale(let locale): return { string in string.capitalized(with: locale) }

case .custom(let transform): return transform
}
}

}
25 changes: 21 additions & 4 deletions Sources/XMLBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ class XMLBuilder: NSObject, XMLParserDelegate {
var styles: [StringStyle]
var xmlStylers: [XMLStyler]

// The XML parser sometimes splits strings, which can break localization-sensitive
// string transforms. Work around this by using the currentString variable to
// accumulate partial strings, and then reading them back out as a single string
// when the current element ends, or when a new one is started.
var currentString: String?

var topStyle: StringStyle {
guard let style = styles.last else { fatalError("Invalid Style Stack") }
return style
Expand Down Expand Up @@ -322,33 +328,44 @@ class XMLBuilder: NSObject, XMLParserDelegate {
xmlStylers.removeLast()
}

func foundNewString() {
guard let newString = currentString else {
return
}
let newAttributedString = topStyle.attributedString(from: newString)
attributedString.append(newAttributedString)
currentString = nil
}

#if swift(>=3.0)
@objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
foundNewString()
enter(element: elementName, attributes: attributeDict)
}

@objc func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
foundNewString()
guard elementName != XMLBuilder.internalTopLevelElement else { return }
exit(element: elementName)
}

@objc func parser(_ parser: XMLParser, foundCharacters string: String) {
let newAttributedString = topStyle.attributedString(from: string)
attributedString.append(newAttributedString)
currentString = (currentString ?? "").appending(string)
}
#else
@objc func parser(parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
foundNewString()
enter(element: elementName, attributes: attributeDict)
}

@objc func parser(parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
foundNewString()
guard elementName != XMLBuilder.internalTopLevelElement else { return }
exit(element: elementName)
}

@objc func parser(parser: XMLParser, foundCharacters string: String) {
let newAttributedString = topStyle.attributedString(from: string)
attributedString.append(newAttributedString)
currentString = (currentString ?? "").appending(string)
}
#endif

Expand Down
Loading

0 comments on commit 79a2c0b

Please sign in to comment.