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>->太字+フォントサイズ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>
...その他多数
...
こうして <a> タグを解析すると、指定した MarkupStyle が適用されます。
HTMLTagName の拡張:
let zhgchgli = ExtendTagName("zhgchgli")
HTML スタイル属性
前述のように、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で変換。



コメント