記事

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

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

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

本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。

記事一覧


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

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

ZhgChgLi / ZMarkupParser

[ZhgChgLi](https://github.com/ZhgChgLi){:target="_blank"} [ZMarkupParser](https://github.com/ZhgChgLi/ZMarkupParser){:target="_blank"}

ZhgChgLi / ZMarkupParser

機能

  • 純粋な Swift で開発されており、正規表現を使って 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: .semilbold & 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](https://quickchart.io/chart-maker/view/zm-73887470-e667-4ca3-8df0-fe3563832b0b){:target="_blank"}

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に対応したラッパーです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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タグに適用したいスタイルを自由に宣言できます:

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

HTMLタグ

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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>
...その他多数
...

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

HTMLTagName の拡張:

1
let zhgchgli = ExtendTagName("zhgchgli")

HTML スタイル属性

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

1
2
3
4
5
6
7
ColorHTMLTagStyleAttribute(), // 色
BackgroundColorHTMLTagStyleAttribute(), // 背景色
FontSizeHTMLTagStyleAttribute(), // フォントサイズ
FontWeightHTMLTagStyleAttribute(), // フォントの太さ
LineHeightHTMLTagStyleAttribute(), // 行間
WordSpacingHTMLTagStyleAttribute(), // 単語間隔
...

Style Attribute の拡張:

1
2
3
4
5
6
7
8
9
ExtendHTMLTagStyleAttribute(styleName: "text-decoration", render: { value in
  var newStyle = MarkupStyle()
  if value == "underline" {
    newStyle.underline = NSUnderlineStyle.single
  } else {
    // ...  
  }
  return newStyle
})

使用

1
2
3
import ZMarkupParser

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

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

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

カスタマイズ

1
2
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 レンダリング

1
2
3
4
5
6
let attributedString = parser.render(htmlString) // NSAttributedString

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

HTMLストリッパー

1
parser.stripper(htmlString)

セレクターHTML文字列

1
2
3
4
5
6
7
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 メソッドを使用してください。

1
2
3
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で変換。


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

Improve this page on Github.

本記事は著者により CC BY 4.0 に基づき公開されています。

© ZhgChgLi. All rights reserved.
閲覧数: 802,415+, 最終更新日時: 2026-01-15 11:14:58 +08:00

本サイトは Chirpy テーマを使用し、Jekyll 上で構築されています。
Medium の記事は ZMediumToMarkdown により変換されています。