GONMarkupParser 0.8.2

GONMarkupParser 0.8.2

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Jul 2020

Maintained by Nicolas Goutaland.



  • By
  • nicolasgoutaland

GONMarkupParser

Easily build NSAttributedString from XML/HTML like strings.

Demo

ScreenShot

TL;DR;

    NSString *inputText = @"Simple input text, using a preconfigured parser.\n<color value=\"red\">This text will be displayed in red</>.\n<font size="8">This one will be displayed in small</>.\nNow a list:\n<ul><li>First item</><li>Second item</><li><color value="blue">Third blue item</></><li><b><color value="green">Fourth bold green item<//>";

    // No custom configuration, use default tags only

    // Affect text to label
    label.attributedText = [[GONMarkupParserManager sharedParser] attributedStringFromString:inputText                  
                                                                                       error:nil];
    // You can also use [label setMarkedUpText:inputText];

ScreenShot

Need a more complex example ?

    NSString *inputText = @"Simple input text, using a preconfigured parser.\n<red>This text will be displayed in red</>.\n<small>This one will be displayed in small</>.\n<pwet>This one is a custom one, to demonstrate how easy it is to declare a new markup.</>";

    // Set your custom configuration here
#ifdef DEBUG
    [GONMarkupParserManager sharedParser].logLevel = GONMarkupParserLogLevelAll; // Fuck yeah, error debugging
#endif
    
    // Set default string configuration
    [[GONMarkupParserManager sharedParser].defaultConfiguration setObject:[UIFont systemFontOfSize:25.0] forKey:NSFontAttributeName];
    
    // Add a custom markup, that will center text when used, and display it in pink.
    NSMutableParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    defaultParagraphStyle.alignment = NSTextAlignmentCenter;
    [[GONMarkupParserManager sharedParser] addMarkup:[GONMarkupSimple simpleMarkup:@"pwet"
                                                                             style:@{
                                                                                     NSParagraphStyleAttributeName : defaultParagraphStyle,
                                                                                     NSForegroundColorAttributeName : [@"pink" representedColor] // NSString+Color
                                                                                     }
                                                                   mergingStrategy:GONMarkupSimpleMergingStrategyMergeAll]];
    
    // Add add font markup, to display small text when encountered
    [[GONMarkupParserManager sharedParser] addMarkup:[GONMarkupNamedFont namedFontMarkup:[UIFont systemFontOfSize:12.0] forTag:@"small"]];

    // Add a convenient tag for red color
    [[GONMarkupParserManager sharedParser] addMarkup:[GONMarkupNamedColor namedColorMarkup:[UIColor redColor]
                                                                                    forTag:@"red"]];

    // Affect text to label
    label.attributedText = [[GONMarkupParserManager sharedParser] attributedStringFromString:inputText                  
                                                                                       error:nil];

ScreenShot

Description

Creating rich text under iOS can be cumbersome, needing a lot of code.
The main goal of GONMarkupParser is to provide an easy to use syntax, near XML/HTML, but more flexible.
Some others projects exists, allowing you to build NSAttributedString from HTML, but my main goal here was to focus on text semantic. In fact, the parser will detect registered markups and apply style on text.
The purpose of this was to be able to generate different outputs from the same input string, without editing its content, but editing the markups style.

GONMarkupParser is not an out of the box solution to parse HTML files.

Installation

CocoaPods: pod 'GONMarkupParser'
Manual: Copy the Classes folder in your project. You will also need to manually install NSString+Color. Seriously, consider using cocoapods instead ;)

Import wanted headers in your project. .pch is a good place ;)
GONMarkupParser_All.h will reference all library headers, whereas GONMarkupDefaultMarkups.h only references default markup classes.

Usage

  • instantiate a new GONMarkupParser or use the + GONMarkupParserManager sharedParser one.
  • configure your parser adding supported tags, default ones, custom ones, etc...
  • parse input string and retrieve result NSMutableAttributedString using - attributedStringFromString:error: method from GONMarkupParser
  • you can also set text on UILabel / UITextField / UITextView / UIButton by using setMarkedUpText: methods

How does it work ?

ScreenShot

To fully understand how style will be applied to string, you have to imagine a LIFO stack composed of style description.
Each time a new markup is found, current style configuration will be saved then stacked. New configuration will be the previous one, updated by current markup configuration.
Each time a closing markup is found, current style configuration is popped out, and previous one restored.

Syntax

Syntax is pretty easy. It's like XML, but non valid one, to be easier and faster to write.

  • Each markup should be contained between < and > characters
  • <strong>
  • Like XML, closing markup should start with / character. You can omit markup name in closing tag. If closing tag isn't matching currently opened one, an error will be generated, no crash will occur and generated text may not be be as expected
  • </strong>, </>
  • You can also close all opened markup by using <//>

Examples

 This is a <strong>valid</strong> string with some <color value="red">red <b>bold text</b></color>.
 This is a <strong>valid</>string with some <color value="red">red <b>bold text</></>.
 This is a <strong>valid</Hakuna> string with some <color value="red">red <b>bold text</mata></ta>. // Will work but generates an error
 This is a <strong>valid</> string with some <color value="red">red <b>bold text<//>.

Parser

Constructor

GONMarkupParser class provide two class constructors.

  • + defaultMarkupParser is a parser with all default tags registered (See Default tags summary for more information)
  • + emptyMarkgiupParser is a parser without any registered tags

Properties

A parser can have a pre / post processing block, that will be called prior and after parsing. This allows you to perform some string replace before parsing for example.

Parsers have two interesting properties :

  • replaceNewLineCharactersFromInputString, is intended to strip all newlines characters from input string. Use br markup to add new lines. Default is NO.
  • replaceHTMLCharactersFromOutputString, is intended to replace all HTML entities contained in string, after parsing. Default is YES.

defaultConfiguration will contains default style configuration for generated attributed string. Content should be valid attributes parameters, as you may pass to - addAttributes:range: of NSMutableAttributedString objects. For default text color, you can set NSForegroundColorAttributeName for example.

For debugging purpose, you can configure debugLevel property.

assertOnError property is also available to generate an assert when an error is encountered.

Configuration

A parser must have some registered markups to correctly handling strings.
Use - addMarkup:, - addMarkups:, - removeMarkups: and - removeAllMarkups methods for that purpose.
A markup can be added to only one parser at a time.

Registered fonts

To simplify fonts uses, you can register then using - registerFont:forKey: method, then referencing them using given key.
Very useful with <font> markup, allowing you to directly use code instead of full font name. You can also use codes such as mainFont, titleFont to easily update them later throught all your strings.

GONMarkupParserManager

sharedParser

A shared parser is available, so you don't have to create one and reference it throught all your application.
Shared parser is configured with all default markups.

Parsers registration

You can register some parser to this class, allowing you to use them from different places in your application.

Available UIKit Categories

UILabel/UITextField/UITextView
2 methods were added to UILabel, UITextField and UITextView, allowing you to easily update its attributed string using a markedup one.

  • - setMarkedUpText:(NSString *)text parser:(GONMarkupParser *)parser will use given parser to handle string and generate attributed one.
  • - setMarkedUpText:(NSString *)text will use shared one, aka [GONMarkupParserManager sharedParser]

If no parser default configuration is set for NSForegroundColorAttributeName, NSFontAttributeName and NSParagraphStyleAttributeName, components textColor, textAlignment and font properties will be used as default.

You are strongly encouraged to use these methods if you want to use your component style as default parser configuration.

Anchor support

Anchor support is supported using <a href="..."> markup.
If NSAttributedString is displayed in a UITextView, you can handle user clicks on it.
Be sure your UITextView is non editable, selectable and have its delegate set.
Then, in your delegate, implements
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
method.

Color style won't be applied to links. You have to use linkTextAttributes attribute from your UITextView to set it.

Default tags

Summary

Tag Class Parameters Effect
left GONMarkupAlignment none Force text alignment to left
right GONMarkupAlignment none Force text alignment to right
center GONMarkupAlignment none Force text alignment to center
justified GONMarkupAlignment none Force text alignment to justified
natural GONMarkupAlignment none Force text alignment to natural
color GONMarkupColor value Set text color. For supported value syntaxes, check NSString+Color representedColor method.
N/A GONMarkupNamedColor none Set text color. Can be used to reset text color to parser default one if specified color is nil
font GONMarkupFont size, name, color Set text font, text size or both. Can be used also to set text color
N/A GONMarkupNamedFont none Set text font and size. Can be used to reset font to parser default one if specified font is nil
br GONMarkupLineBreak none Add a new line
ul GONMarkupList none Create an unordered list
ol GONMarkupList none Create an ordered list
li GONMarkupListItem none Add a list item to current list
p GONMarkupParagraph none Specify a paragraph. A paragraph will automatically insert a new blanck line after it
inc GONMarkupInc value Increment text font size. If value is missing, font will be increased by one point
dec GONMarkupDec value Decrement text font size. If value is missing, font will be decreased by one point
reset GONMarkupReset all All enclosed text will use default parser configuration
N/A GONMarkupSimple none Apply a configuration to enclosed text
b GONMarkupBold none Set text to bold. Allows user to define an override block overrideBlock to provide another font. Useful to provide a medium font instead of bold one for example.
strong GONMarkupStrong none Set text to strong (bold). Allows user to define an override block overrideBlock to provide another font. Useful to provide a medium font instead of bold one for example.
i GONMarkupItalic none Set text to italic. Allows user to define an override block overrideBlock to provide another font. Useful to provide a medium italic font instead of bold italic one for example.
sup GONMarkupTextStyle none Set text to superscript
sub GONMarkupTextStyle none Set text to subscript
u GONMarkupLineStyle words to apply style only on words (true, false), pattern (solid, dot, dash, dashdot, dashdotdot), style (single, thick, double) and color (Check NSString+Color representedColor method) Underline text
strike GONMarkupLineStyle words to apply style only on words (true, false), pattern (solid, dot, dash, dashdot, dashdotdot), style (single, thick, double) and color (Check NSString+Color representedColor method) strikethrough text
a GONMarkupAnchor href link value Support an anchor link. See Anchor support for more information.
N/A GONMarkupBlock none When encountered executes associated block

Reset

Reset is a special tag, allowing you to protect some parts of a string. You can also force markup to ignore default parser configuration by setting all attribute.

ScreenShot

How to add new markup

You can add new markup in your application, to add new style, or to just add some semantic to your text, allowing you to update rendering, without changing input string.
There is 3 ways to do it.

Adding a new simple marker

The simpler way to add a new markup in your application is to use one of theses 3 following classes :

  • GONMarkupNamedColor, allows you to add a markup that updates text color
  • GONMarkupNamedFont, allows you to add a markup that updates text font
  • GONMarkupSimple, allows you to add a markup that updates all text attributes. Dictionary is intended to be the same as you may pass to configure an NSMutableAttributedString using -setAttributes:range: method.

Example

    // Retrieve shared parser
    GONMarkupParser *parser = [GONMarkupParserManager sharedParser];
    
    // Add a named color markup
    [parser addMarkup:[GONMarkupNamedColor namedColorMarkup:[UIColor redColor]
               forTag:@"red"]];

    // Add a named font markup
    [parser addMarkup:[GONMarkupNamedFont namedFontMarkup:[UIFont systemFontOfSize:12.0] 
               forTag:@"small"]];

    // Add a custom markup, that will center text when used, and display it in pink.
    NSMutableParagraphStyle *defaultParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    defaultParagraphStyle.alignment = NSTextAlignmentCenter;
    [parser addMarkup:[GONMarkupSimple simpleMarkup:@"pwet"
                                              style:@{
                                                        NSParagraphStyleAttributeName : defaultParagraphStyle,
                                                        NSForegroundColorAttributeName : [@"pink" representedColor] // NSString+Color
                                                     }
                                    mergingStrategy:GONMarkupSimpleMergingStrategyMergeAll]];

Adding a new block based marker

For more complexe markup, you can add GONMarkupBlock instances.

It have blocks 5 parameters :

  • openingMarkupBlock, called when markup opening is found. Used to pushed your custom configuration to stack
  • closingMarkupBlock, called once markup is closed.
  • updatedContentStringBlock, called right after closingMarkupBlock, allowing you to override returned string
  • prefixStringForContextBlock, called right after openingMarkupBlock, allowing you to return a prefix
  • suffixStringForContextBlock, called right after openingMarkupBlock, allowing you to return a suffix

Example

    // Retrieve shared parser
    GONMarkupParser *parser = [GONMarkupParserManager sharedParser];
    
    // Custom markup, based on block
    GONMarkupBlock *markupBlock = [GONMarkupBlock blockMarkup:@"custom"];
    markupBlock.openingMarkupBlock = ^(NSMutableDictionary *configurationDictionary, NSString *tag, NSMutableDictionary *context, NSDictionary *attributes) {
        // Update font size
        [configurationDictionary setObject:[UIFont boldSystemFontOfSize:69.0]
                                    forKey:NSFontAttributeName];
        
        // Update text color
        [configurationDictionary setObject:[@"brown" representedColor]
                                    forKey:NSForegroundColorAttributeName];
    };

    [parser addMarkup:markupBlock];

Creating a new GONMarkup subclass

You can add a custom markup by subclassing GONMarkup class.

Adding a new markup by subclassing is useful if you want to reuse your markups between several projets, or to implement more complex behavior. When subclassing, you have access to a shared object, allowing you to persists data and share it between each markup handling.

For examples, have a look a currently defined markups ;) See GONMarkupList and GONMarkupListItem for an implementation using shared context.

Troubleshooting

Some text is missing

Check that your markup is correctly registered and that your tags are right balanced.

When using < / >, some text is missing

Use &lt; and &gt; in text.

Text color is still applied after my tag is closed.

This is caused by NSAttributedString internal behavior. Once a color is set, it is applied until a new one is set.
To prevent this problem, be sure to have set a default text color in your parser (defaultConfiguration / NSForegroundColorAttributeName key). You can use setMarkedUpText: on UILabel / UITextField to use default component configuration.

Text style isn't applied to my link

See Anchor support for more information.

I am encountering some crashes when using custom font

Be sure to use correct font name, or that font code you are using is correctly registered to your parser.
Want to dump all available fonts on your device and check real names ?
Have a look at DUMP_FONT_LIST() here

No new lines are inserted using <br>

<br> alone is not valid in GONMarkupParser. Be sure to use <br/>.

Color isn't applied

Check that you color value synthax is correct.
For more information on supported synthax, have a look at NSString+UIColor here, that is used to compute colors from your string values.

Did Kim Kardashian broke the Internet ?

No, definitely not. I was still able to push to GitHub yesterday.

Evolutions

  • Allow indentation prefix in lists customisation
  • Implement NSCoder in parser and Markers
  • Allow copy on parsers / markers
  • Improve closing tag

Contributors

See the Contributors page on github.

Changelog

Changelog can be found here