ZMarkupParser

repository·main·Indexed 18 days ago

https://github.com/zhgchgli/zmarkupparser

A pure-Swift library for converting HTML strings into NSAttributedString. It features automatic HTML correction, custom tag and style support, and thread-safe rendering without requiring WebKit. Designed for partial HTML content, it provides a ZHTMLParserBuilder for configuration and offers better stability and performance than NSAttributedString.DocumentType.html, particularly for inputs exceeding 54,600 characters.

Tokens
3K
Snippets
10
Records
15
Agent score
13%

What's inside ZMarkupParser

  1. Important limitations and rendering notes

    main

    When using ZMarkupParser, keep the following in mind:

    • Link Styling: To change link colors in a UITextView, you must set the linkTextAttributes property to an NSAttributedString.Key containing the desired styles. Note that UILabel does not support changing .link text color via NSAttributedString.Key.foregroundColor.
    • Complexity: This library is designed for rendering partial HTML content. It is not suitable for very large or highly complex HTML documents; for those use cases, a WKWebView is recommended.
  2. Install ZMarkupParser via Swift Package Manager

    main

    To add ZMarkupParser to your Swift project using SPM, add the repository URL and specify the version requirement. It is recommended to use "Up to Next Major" starting from version 2.0.2.

    // In your Package.swift
    ...
    dependencies: [
      .package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "2.0.2"),
    ]
    ...
    .target(
        ...
        dependencies: [
            "ZMarkupParser",
        ],
        ...
    )
  3. Install ZMarkupParser via CocoaPods

    main

    To install via CocoaPods, add the following to your Podfile. Ensure your platform is set to at least iOS 12.0 and use_frameworks! is enabled.

    source 'https://github.com/CocoaPods/Specs.git'
    platform :ios, '12.0'
    use_frameworks!
    
    target 'MyApp' do
      pod 'ZMarkupParser', '~> 2.0.2'
    end
  4. Build a parser using ZHTMLParserBuilder

    main

    ZMarkupParser uses the Builder pattern to configure the parser. You start with ZHTMLParserBuilder.initWithDefault(), which includes all pre-defined HTML tag names and style attributes. You can then chain methods to set a root style, add custom tags, or map CSS classes and IDs to specific MarkupStyle objects before calling .build() to create the parser instance.

    import ZMarkupParser
    
    // Basic initialization with a root style
    let parser = ZHTMLParserBuilder.initWithDefault()
        .set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13)).build())
        .build()
  5. Handle large or untrusted HTML inputs safely

    main

    If your application needs to process long-form HTML or untrusted user input that may exceed 54,600 characters, do not use NSAttributedString.DocumentType.html, as it is prone to system crashes at that threshold.

    ZMarkupParser is designed to handle large inputs safely. Its performance scales roughly linearly with input length above small inputs (i ≈ 10), making it suitable for high-volume or large-document parsing pipelines.

  6. Compare ZMarkupParser performance with NSAttributedString

    main

    When choosing between ZMarkupParser and the system's NSAttributedString.DocumentType.html, consider both speed and stability. ZMarkupParser is approximately 19% faster for standard HTML samples (~100 KB) and, crucially, does not crash on large inputs.

    Key Performance Differences:

    • Speed: For a ~100 KB HTML sample, ZMarkupParser averages ~0.372s per iteration, compared to ~0.457s for the system API.
    • Stability/Limits: The system API (NSAttributedString.DocumentType.html) is documented to crash when input exceeds approximately 54,600 characters. ZMarkupParser handles much larger inputs (e.g., 334,000 characters) linearly without crashing.
  7. Strip and Select HTML content

    main

    ZMarkupParser provides tools for manipulating HTML content beyond simple rendering:

    • Stripping: Use parser.stripper(htmlString) to get an NSAttributedString with HTML tags removed.
    • Selecting: Use parser.selector(htmlString) to get an HTMLSelector. This allows you to traverse the HTML structure (e.g., finding specific nested tags) and either retrieve the attributedString for a node or convert the filtered results into a dictionary/JSON string.
    // Stripping tags
    let stripped = parser.stripper(htmlString)
    
    // Selecting specific elements
    let selector = parser.selector(htmlString) // e.g. input: <a><b>Test</b>Link</a>
    
    // Get attributed string of a nested element
    let text = selector.first("a")?.first("b")?.attributedString
    
    // Filter and get as dictionary or JSON string
    let dict = selector.filter("a").get()
    let json = selector.filter("a") // returns JSON string
    
    // Render a specific selected element
    if let element = selector.first("a")?.first("b") {
        let rendered = parser.render(element)
    }
  8. Configure MarkupStyle for NSAttributedString attributes

    main

    The MarkupStyle object is a wrapper used to define the attributes applied to an NSAttributedString. You can initialize it with various properties to control the visual appearance of the parsed text.

    Key properties include:

    • font: MarkupStyleFont
    • foregroundColor: MarkupStyleColor?
    • backgroundColor: MarkupStyleColor?
    • underlineStyle: NSUnderlineStyle?
    • strikethroughStyle: NSUnderlineStyle?
    • link: URL?
    • attachment: NSTextAttachment?

    Example of creating a custom style:

    MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))
  9. Extend HTML tag style attributes

    main

    ZMarkupParser uses HTMLTagStyleAttribute classes to convert HTML style attributes (like style="color:red") into NSAttributedString attributes.

    Pre-defined attributes include:

    • ColorHTMLTagStyleAttribute() for color
    • BackgroundColorHTMLTagStyleAttribute() for background-color
    • FontSizeHTMLTagStyleAttribute() for font-size
    • FontWeightHTMLTagStyleAttribute() for font-weight

    To support a custom CSS-like style attribute (e.g., text-decoration), use ExtendHTMLTagStyleAttribute. This method takes a styleName and a closure that receives the existing style and the new attribute value, returning a modified style.

    ExtendHTMLTagStyleAttribute(styleName: "text-decoration", render: { fromStyle, value in
      var newStyle = fromStyle
      if value == "underline" {
        newStyle.underline = NSUnderlineStyle.single
      } else {
        // ...  
      }
      return newStyle
    })
  10. Extend HTML tag support with custom tags

    main
    If you need to support HTML tags that are not pre-defined, use the ExtendTagName(tagName: String) method. This allows you to create a custom tag name and map it to your own abstract markup class design.
  11. Customize or extend HTML tags

    main

    You can customize the rendering of existing tags or create entirely new ones using the .add(_:withCustomStyle:) method on the builder.

    • Customize existing tags: Pass an instance of HTMLTagName (e.g., B_HTMLTagName()) and a MarkupStyle.
    • Extend with new tag names: Use ExtendTagName("your-tag-name") to define how custom tags should be rendered.
    // Customize an existing tag (e.g., <b>)
    let parser = ZHTMLParserBuilder.initWithDefault()
        .add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold))))
        .build()
    
    // Extend with a custom tag name (e.g., <zhgchgli>)
    let parser = ZHTMLParserBuilder.initWithDefault()
        .add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine)))
        .build()
  12. Use Async methods for large HTML strings

    main

    For performance, if you are processing very large HTML strings, use the asynchronous versions of the core methods to avoid blocking the main thread.

    // Async versions of core operations
    parser.render(htmlString) { attributedString in 
        // handle result
    }
    
    parser.stripper(htmlString) { attributedString in 
        // handle result
    }
    
    parser.selector(htmlString) { selector in 
        // handle result
    }