ZMarkupParser HTML文字列をNSAttributedStringに変換するツール
HTML文字列をNSAttributedStringに変換する際の対応キーとスタイル設定
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タグを拡張しても迅速に対応可能です。 -
styleHTML属性からのスタイル解析拡張をサポート
HTMLではstyle属性で文字スタイルを指定できますが、本パッケージもstyleからのスタイル指定をサポートしています。
例:<b style=”font-size: 20px”></b>->太字+フォントサイズ20px -
iOS/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 に対応したラッパーです。
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 によって変換されました。



コメント