ZMarkupParser|HTML StringをNSAttributedStringに高速変換するツール—スタイル設定対応
HTML Stringを正確にNSAttributedStringへ変換し、カスタムキーのスタイル設定も簡単に実装。iOS開発者向けに変換効率を向上させ、UI表現力を強化します。
本記事は AI による翻訳をもとに作成されています。表現が不自然な箇所がありましたら、ぜひコメントでお知らせください。
記事一覧
ZMarkupParser HTML文字列をNSAttributedStringに変換するツール
HTML文字列をNSAttributedStringに変換する際のキー対応スタイル設定
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タグの拡張も迅速に対応可能です。styleHTML属性からのスタイル解析拡張をサポート
HTMLではstyle属性で文字スタイルを指定できますが、本パッケージもstyleからのスタイル指定をサポートしています。
例:<b style=”font-size: 20px”></b>->太字+フォントサイズ20pxiOS/macOS をサポート
HTMLのカラー名からUIColor/NSColorへの対応
テストカバレッジ:80%以上
<img>画像、<ul>項目リスト、<table>表などの HTMLタグ解析をサポートNSAttributedString.DocumentType.htmlより高いパフォーマンス
パフォーマンス分析
テスト環境: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で変換。
本記事は Medium にて初公開されました(こちらからオリジナル版を確認)。ZMediumToMarkdown による自動変換・同期技術を使用しています。

{:target="_blank"}](/assets/a5643de271e4/1*UPkmp2XsUjlVe_TmOur_3A.webp)
