ZhgChg.Li

ZMarkupParser|HTML StringをNSAttributedStringに高速変換するツール—スタイル設定対応

HTML Stringを正確にNSAttributedStringへ変換し、カスタムキーのスタイル設定も簡単に実装。iOS開発者向けに変換効率を向上させ、UI表現力を強化します。

ZMarkupParser|HTML StringをNSAttributedStringに高速変換するツール—スタイル設定対応
本記事は AI による翻訳です。お気づきの点があればお知らせください。

ZMarkupParser HTML文字列をNSAttributedStringに変換するツール

HTML文字列をNSAttributedStringに変換する際の対応キーとスタイル設定

ZhgChgLi / ZMarkupParser

ZhgChgLi / ZMarkupParser

ZhgChgLi / ZMarkupParser

機能

  • 純粋な Swift で開発されており、Regex を使って HTML タグを解析し、トークン化を経てタグの正確性を検証・修正(終了タグのないタグや位置のずれたタグの修正)し、抽象構文木に変換します。最終的に Visitor パターンを使って HTML タグと抽象スタイルを対応させ、最終的な NSAttributedString を生成します。なお、いかなるパーサーライブラリにも依存していません。

  • HTMLレンダリング(NSAttributedStringへの変換)/ストリッパー(HTMLタグの除去)/セレクター機能をサポート

  • 自動でタグの正確性を解析・修正(終了タグがないタグや誤った位置のタグを修正)
    <br> -> <br/>
    <b>Bold<i>Bold+Italic</b>Italic</i> -> <b>Bold<i>Bold+Italic</i></b><i>Italic</i>
    <Congratulation!> -> <Congratulation!>(文字列として扱う)

  • カスタムスタイル指定のサポート
    例: <b></b> -> weight: .semibold & underline: 1

  • 支援カスタムHTMLタグの解析
    例:<zhgchgli></zhgchgli> を希望のスタイルに解析する

  • アーキテクチャ設計を含み、HTMLタグの拡張が容易です。
    現在は基本的なスタイルに加え、ul/ol/li リストや hr 区切り線のレンダリングをサポートしており、将来的に他のHTMLタグを拡張しても迅速に対応可能です。

  • style HTML属性からのスタイル解析拡張をサポート
    HTMLではstyle属性で文字スタイルを指定できますが、本パッケージもstyleからのスタイル指定をサポートしています。
    例: <b style=”font-size: 20px”></b> -> 太字+フォントサイズ20px

  • iOS/macOSをサポートしています

  • HTMLカラー名からUIColor/NSColorへの対応

  • テストカバレッジ:80%以上

  • <img> 画像、<ul> 項目リスト、<table> 表などの HTMLタグ解析をサポート

  • NSAttributedString.DocumentType.html より高いパフォーマンス

パフォーマンス分析

Performance Benchmark

Performance Benchmark

  • テスト環境:2022/M2/24GB メモリ/macOS 13.2/XCode 14.1

  • X軸:HTML 文字数

  • Y軸:レンダリングにかかる時間(秒)

*また、NSAttributedString.DocumentType.html は54,600文字を超える長い文字列でクラッシュ(EXC_BAD_ACCESS)します。

お試しプレイ

プロジェクトを直接ダウンロードして ZMarkupParser.xcworkspace を開き、ZMarkupParser-Demo ターゲットを選択してビルド&実行することで、すぐに動作をテストできます。

インストール

SPM/Cocoapods に対応しています。詳細は Readme をご覧ください。

使用方法

スタイル宣言

MarkupStyle/MarkupStyleColor/MarkupStyleParagraphStyle は、NSAttributedString.Key に対応したラッパーです。

var font:MarkupStyleFont
var paragraphStyle:MarkupStyleParagraphStyle
var foregroundColor:MarkupStyleColor? = nil
var backgroundColor:MarkupStyleColor? = nil
var ligature:NSNumber? = nil
var kern:NSNumber? = nil
var tracking:NSNumber? = nil
var strikethroughStyle:NSUnderlineStyle? = nil
var underlineStyle:NSUnderlineStyle? = nil
var strokeColor:MarkupStyleColor? = nil
var strokeWidth:NSNumber? = nil
var shadow:NSShadow? = nil
var textEffect:String? = nil
var attachment:NSTextAttachment? = nil
var link:URL? = nil
var baselineOffset:NSNumber? = nil
var underlineColor:MarkupStyleColor? = nil
var strikethroughColor:MarkupStyleColor? = nil
var obliqueness:NSNumber? = nil
var expansion:NSNumber? = nil
var writingDirection:NSNumber? = nil
var verticalGlyphForm:NSNumber? = nil
...

自分で適用したい HTML タグに対応するスタイルを自由に宣言できます:

let myStyle = MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))

HTMLタグ

レンダリングする HTML タグと対応するマークアップスタイルを宣言します。現在定義されている HTML タグ名は以下の通りです:

A_HTMLTagName(), // <a></a>
B_HTMLTagName(), // <b></b>
BR_HTMLTagName(), // <br></br>
DIV_HTMLTagName(), // <div></div>
HR_HTMLTagName(), // <hr></hr>
I_HTMLTagName(), // <i></i>
LI_HTMLTagName(), // <li></li>
OL_HTMLTagName(), // <ol></ol>
P_HTMLTagName(), // <p></p>
SPAN_HTMLTagName(), // <span></span>
STRONG_HTMLTagName(), // <strong></strong>
U_HTMLTagName(), // <u></u>
UL_HTMLTagName(), // <ul></ul>
DEL_HTMLTagName(), // <del></del>
IMG_HTMLTagName(handler: ZNSTextAttachmentHandler), // <img> と画像ダウンローダー
TR_HTMLTagName(), // <tr>
TD_HTMLTagName(), // <td>
TH_HTMLTagName(), // <th>
...その他多数
...
// HTMLTag
HTMLTag(tagName: A_HTMLTagName(), customStyle: myStyle)

こうすることで、<a> タグを解析する際に指定した MarkupStyle が適用されます。

HTMLTagName の拡張:

let zhgchgli = ExtendTagName("zhgchgli")

HTML Style 属性

前述のように、HTML は Style Attribute からスタイルを指定できます。ここでは指定可能なスタイルの抽象化と拡張を行っており、現在定義されている HTML Style Attribute は以下の通りです:

ColorHTMLTagStyleAttribute(), // 色
BackgroundColorHTMLTagStyleAttribute(), // 背景色
FontSizeHTMLTagStyleAttribute(), // フォントサイズ
FontWeightHTMLTagStyleAttribute(), // フォントの太さ
LineHeightHTMLTagStyleAttribute(), // 行の高さ
WordSpacingHTMLTagStyleAttribute(), // 単語間隔
...

Style Attribute の拡張:

ExtendHTMLTagStyleAttribute(styleName: "text-decoration", render: { value in
  var newStyle = MarkupStyle()
  if value == "underline" {
    newStyle.underline = NSUnderlineStyle.single
  } else {
    // ...
  }
  return newStyle
})

使用

import ZMarkupParser

let parser = ZHTMLParserBuilder.initWithDefault().set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13)).build()

initWithDefault はあらかじめ定義された HTML タグ名とデフォルト対応の MarkupStyle、さらにあらかじめ定義された Style Attribute を自動的に追加します。

set(rootStyle:) は、文字列全体のデフォルトスタイルを指定できますが、省略も可能です。

カスタマイズ

let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build() // 指定した markupstyle を使って拡張 HTML タグ <zhgchgli></zhgchgli> をレンダリングします
let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))).build() // デフォルトの太字スタイルの代わりに、指定した markupstyle を使って <b></b> をレンダリングします

HTML レンダリング

let attributedString = parser.render(htmlString) // NSAttributedString

// UITextViewで使用
textView.setHtmlString(htmlString)
// UILabelで使用
label.setHtmlString(htmlString)

HTML ストリッパー

parser.stripper(htmlString)

(コード内のコメントはありませんので、そのまま保持しています。)

セレクター HTML 文字列

let selector = parser.selector(htmlString) // HTMLSelector 例: 入力 <a><b>Test</b>Link</a>
selector.first("a")?.first("b").attributedString // "Test" を返す
selector.filter("a").attributedString // "Test Link" を返す

// selector の結果からレンダリング
let selector = parser.selector(htmlString) // HTMLSelector 例: 入力 <a><b>Test</b>Link</a>
parser.render(selector.first("a")?.first("b"))

非同期

また、長い文字列をレンダリングする場合は、UIのフリーズを防ぐために async メソッドを使用してください。

parser.render(String) { _ in }...
parser.stripper(String) { _ in }...
parser.selector(String) { _ in }...

ノウハウ

  • UITextView 内のハイパーリンクのスタイルは linkTextAttributes を参照するため、NSAttributedString.key を設定しても効果が現れない場合があります。

  • UILabel は指定した URL スタイルをサポートしていないため、NSAttributedString.key を設定しても効果が現れない場合があります。

  • 複雑なHTMLをレンダリングする場合は、やはりWKWebView(JSや表のレンダリングを含む)を使用する必要があります。

技術的原理と開発ストーリー:「 手作りHTMLパーサーのあれこれ

ご協力とIssueの提出を歓迎します。できるだけ早く修正します。

Post Medium から ZMediumToMarkdown によって変換されました。

GitHub で編集
この記事を改善
本記事は Medium で初公開
オリジナルを読む
この記事をシェア
リンクをコピー · SNS でシェア
ZhgChgLi
著者

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

コメント