All Projects → zacwest → Zswtaggedstring

zacwest / Zswtaggedstring

Licence: mit
Converts Strings into NSAttributedStrings using an HTML-like markup language.

Programming Languages

swift
15916 projects

Labels

Projects that are alternatives of or similar to Zswtaggedstring

Php Confusable Homoglyphs
A PHP port of https://github.com/vhf/confusable_homoglyphs
Stars: ✭ 27 (-72.45%)
Mutual labels:  string
Cape
String encryption for Arduino, limited microcontrollers and other embedded systems.
Stars: ✭ 58 (-40.82%)
Mutual labels:  string
Better Strings
Java String Interpolation Plugin
Stars: ✭ 66 (-32.65%)
Mutual labels:  string
Arccstr
Thread-safe, reference-counted null-terminated immutable Rust strings.
Stars: ✭ 34 (-65.31%)
Mutual labels:  string
Xible
Visualize your workflow
Stars: ✭ 49 (-50%)
Mutual labels:  string
String Calc
PHP calculator library for mathematical terms (expressions) passed as strings
Stars: ✭ 60 (-38.78%)
Mutual labels:  string
Stringplus
Funny and minimal string library for C++ inspired by underscore.string
Stars: ✭ 7 (-92.86%)
Mutual labels:  string
String To Stream
Convert a string into a stream (streams2)
Stars: ✭ 75 (-23.47%)
Mutual labels:  string
Golang Combinations
Golang library which provide an algorithm to generate all combinations out of a given string array.
Stars: ✭ 51 (-47.96%)
Mutual labels:  string
String Pixel Width
Blazingly fast measure string width in pixels on the server in Javascript (Node.Js)
Stars: ✭ 65 (-33.67%)
Mutual labels:  string
Cracking The Coding Interview
Solutions for Cracking the Coding Interview - 6th Edition
Stars: ✭ 35 (-64.29%)
Mutual labels:  string
Tinystr
A small ASCII-only bounded length string representation.
Stars: ✭ 48 (-51.02%)
Mutual labels:  string
String Interner
A data structure to efficiently intern, cache and restore strings.
Stars: ✭ 60 (-38.78%)
Mutual labels:  string
Mightystring
Making Ruby Strings Powerful
Stars: ✭ 28 (-71.43%)
Mutual labels:  string
Unis
UNIS: A Common Architecture for String Utilities within the Go Programming Language.
Stars: ✭ 69 (-29.59%)
Mutual labels:  string
Xformat
Multi-syntax printf
Stars: ✭ 8 (-91.84%)
Mutual labels:  string
Str
A SIMD optimized fixed-length string class along with an adaptive hash table for fast searching
Stars: ✭ 60 (-38.78%)
Mutual labels:  string
String Extra
Unicode/String support for Twig
Stars: ✭ 92 (-6.12%)
Mutual labels:  string
Dashify
Convert a camelcase or space-separated string to a dash-separated string.
Stars: ✭ 71 (-27.55%)
Mutual labels:  string
Fuzzy Swift
🔍 simple and fast fuzzy string matching in Swift
Stars: ✭ 61 (-37.76%)
Mutual labels:  string

ZSWTaggedString

CI Status Version License Platform

ZSWTaggedString converts a String/NSString marked-up with tags into an NSAttributedString. Tags are similar to HTML except you define what each tag represents.

The goal of this library is to separate presentation from string generation while making it easier to create attributed strings. This way you can decorate strings without concatenating or using hard-to-localize substring searches.

The most common example is applying a style change to part of a string. Let's format a string like "bowties are cool":

let localizedString = NSLocalizedString("bowties are <b>cool</b>", comment: "");
let taggedString = ZSWTaggedString(string: localizedString)

let options = ZSWTaggedStringOptions()

options["b"] = .static([
    .font: UIFont.boldSystemFont(ofSize: 18.0)
])

let attributedString = try! taggedString.attributedString(with: options)
print(attributedString)
NSString *localizedString = NSLocalizedString(@"bowties are <b>cool</b>", nil);
ZSWTaggedString *taggedString = [ZSWTaggedString stringWithString:localizedString];

ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];
[options setAttributes:@{
    NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0]
          } forTagName:@"b"];

NSLog(@"%@", [taggedString attributedStringWithOptions:options]);

This produces an attributed string where the "cool" substring is bold, and the rest undefined:

bowties are {
}cool{
    NSFont = "<UICTFont: …> …; font-weight: bold; …; font-size: 18.00pt";
}

Dynamic attributes

You can apply style based on metadata included in the string. Let's italicize a substring while changing the color of a story based on its type:

let story1 = Story(type: .One, name: "on<e")
let story2 = Story(type: .Two, name: "tw<o")

func storyWrap(_ story: Story) -> String {
    // You should separate data-level tags from the localized strings
    // so you can iterate on their definition without the .strings changing
    // Ideally you'd place this on the Story class itself.
    return String(format: "<story type='%d'>%@</story>",
        story.type.rawValue, ZSWEscapedStringForString(story.name))
}

let format = NSLocalizedString("Pick: %@ <i>or</i> %@", comment: "On the story, ...");
let string = ZSWTaggedString(format: format, storyWrap(story1), storyWrap(story2))

let options = ZSWTaggedStringOptions()

// Base attributes apply to the whole string, before any tag attributes.
options.baseAttributes = [
    .font: UIFont.systemFont(ofSize: 14.0),
    .foregroundColor: UIColor.gray
]

// Normal attributes just add their attributes to the attributed string.
options["i"] = .static([
    .font: UIFont.italicSystemFont(ofSize: 14.0)
])

// Dynamic attributes give you an opportunity to decide what to do for each tag
options["story"] = .dynamic({ tagName, tagAttributes, existingAttributes in
    var attributes = [NSAttributedString.Key: AnyObject]()
    
    guard let typeString = tagAttributes["type"] as? String,
        let type = Story.StoryType(rawValue: typeString) else {
            return attributes
    }
    
    switch type {
    case .One:
        attributes[.foregroundColor] = UIColor.red
    case .Two:
        attributes[.foregroundColor] = UIColor.orange
    }
    
    return attributes
})
Story *story1 = …, *story2 = …;

NSString *(^sWrap)(Story *) = ^(Story *story) {
    // You should separate data-level tags from the localized strings
    // so you can iterate on their definition without the .strings changing
    // Ideally you'd place this on the Story class itself.
    return [NSString stringWithFormat:@"<story type='%@'>%@</story>",
            @(story.type), ZSWEscapedStringForString(story.name)];
};

NSString *fmt = NSLocalizedString(@"Pick: %@ <i>or</i> %@", @"On the story, ...");
ZSWTaggedString *string = [ZSWTaggedString stringWithFormat:fmt,
                           sWrap(story1), sWrap(story2)];

ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];

// Base attributes apply to the whole string, before any tag attributes.
[options setBaseAttributes:@{
    NSFontAttributeName: [UIFont systemFontOfSize:14.0],
    NSForegroundColorAttributeName: [UIColor grayColor]
 }];

// Normal attributes just add their attributes to the attributed string.
[options setAttributes:@{
    NSFontAttributeName: [UIFont italicSystemFontOfSize:14.0]
          } forTagName:@"i"];

// Dynamic attributes give you an opportunity to decide what to do for each tag
[options setDynamicAttributes:^(NSString *tagName,
                                NSDictionary *tagAttributes,
                                NSDictionary *existingStringAttributes) {
    switch ((StoryType)[tagAttributes[@"type"] integerValue]) {
        case StoryTypeOne:
            return @{ NSForegroundColorAttributeName: [UIColor redColor] };
        case StoryTypeTwo:
            return @{ NSForegroundColorAttributeName: [UIColor orangeColor] };
    }
    return @{ NSForegroundColorAttributeName: [UIColor blueColor] };
} forTagName:@"story"];

Your localizer now sees a more reasonable localized string:

	/* On the story, ... */
	"Pick: %@ <i>or</i> %@" = "Pick: %[email protected] <i>or</i> %[email protected]";

And you don't have to resort to using .rangeOfString() to format any of the substrings, which is very difficult to accomplish with what we desired above.

There are two types of dynamic attributes you can use: a tag-specific one like above, or the options-global unknownTagAttributes (unknownTagDynamicAttributes in Objective-C) which is invoked when an undefined tag is found. Both have three parameters:

  1. tagName The name of the tag, e.g. story above.
  2. tagAttributes The attributes of the tag, e.g. ["type": "1"] like above.
  3. existingStringAttributes The string attributes that exist already, e.g. [NSForegroundColorAttributeName: UIColor.redColor()]

You can use the existingStringAttributes to handle well-established keys. For example, let's make the <b>, <i>, and <u> tags automatically:

let options = ZSWTaggedStringOptions()

options.baseAttributes = [
    .font: UIFont.systemFont(ofSize: 12.0)
]

options.unknownTagAttributes = .dynamic({ tagName, tagAttributes, existingAttributes in
    var attributes = [NSAttributedString.Key: Any]()
    
    if let font = existingAttributes[.font] as? UIFont {
        switch tagName {
        case "b":
            attributes[.font] = UIFont.boldSystemFont(ofSize: font.pointSize)
        case "i":
            attributes[.font] = UIFont.italicSystemFont(ofSize: font.pointSize)
        default:
            break
        }
    }
    
    if tagName == "u" {
        attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
    }
    
    return attributes
})
ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];
[options setBaseAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:12.0] }];

[options setUnknownTagDynamicAttributes:^(NSString *tagName,
                                          NSDictionary *tagAttributes,
                                          NSDictionary *existingStringAttributes) {
    BOOL isBold = [tagName isEqualToString:@"b"];
    BOOL isItalic = [tagName isEqualToString:@"i"];
    BOOL isUnderline = [tagName isEqualToString:@"u"];
    UIFont *font = existingStringAttributes[NSFontAttributeName];

    if ((isBold || isItalic) && font) {
        if (isBold) {
            return @{ NSFontAttributeName: [UIFont boldSystemFontOfSize:font.pointSize] };
        } else if (isItalic) {
            return @{ NSFontAttributeName: [UIFont italicSystemFontOfSize:font.pointSize] };
        }
    } else if (isUnderline) {
        return @{ NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) };
    }
    
    return @{};
}];

The library does not provide this functionality by default because custom or inexplicit fonts and dynamic type make this behavior unpredictable. You can use ZSWTaggedStringOptions.registerDefaultOptions() to keep a global default set of options with something like the above.

Fast stripped strings

Stripping tags allows you to create a String for fast height calculations (assuming no font changes), statistics gathering, etc., without the overhead of tags. You can accomplished this by using the .string() method on a ZSWTaggedString instead of the .attributedString() methods.

Error handling

Invalid strings such as non-ending tags (<a>taco!) or strings where you do not escape user input (see Gotchas) are considered errors by the programmer.

For Swift consumers, all of the methods throw when you provide invalid input.

For Objective-C consumers, there are optional NSError-returning methods, and all of the methods return nil in the error case.

Gotchas

If any of your composed strings contain a < character without being in a tag, you must wrap the string with ZSWEscapedStringForString(). In practice, user-generated content where this is important is rare, but you must handle it.

Installation

ZSWTaggedString is available through CocoaPods. Add the following line to your Podfile:

pod "ZSWTaggedString", "~> 4.2"
pod "ZSWTaggedString/Swift", "~> 4.2" # Optional, for Swift support

License

ZSWTaggedString is available under the MIT license. If you are contributing via pull request, please include an appropriate test for the bug you are fixing or feature you are adding.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].