2007-11-03 Excel VBAで実体参照のエンコードとデコード
Excel VBAでHTMLの実体参照をエンコード・デコードするコードを作りました。コードそのものはVB6.0処理系汎用であり,Excel VBA以外でも使えると思います。
あらまし
「HTMLの実体参照」とは,HTMLのメタ文字をエスケープするための記述方法です。メタ文字とは,「タグ」を書くときに使う文字です(正確にはちょっと違うけど)。たとえば「<>」を書きたいときは,「< > 」と書かなければいけません。「<>」はHTMLのメタ文字なので,これらを直接,書いてしまうと「タグ」と区別がつかなくなってしまうのです。
Excelで実体参照の混ざった文字列を扱うことは,あまりないと思いますが,あまりないがために必要に迫られたとき困るのです。
動作概要
- 実体参照を解決するクラスモジュール(HTMLEntity)を作る
- 「実体参照」と「文字」の対応表を作ることで,双方を変換できるようにする
- 実体参照のパターン抽出には正規表現(Scripting.Regexオブジェクト)を使う
仕様
- エンコード対象の文字は「&<>"」のみ
- デコードは文字実体参照と数値文字参照(10進と16進)に対応
- 誤記により実体参照の末尾の「;(セミコロン)」が存在しなかった場合も,なるべくデコードする
- 対応する文字実体参照は「ISO 8859-1」「一般記号,数学記号,ギリシア文字」「マーク付け記号,国際化文字」だが,デコードした結果のすべての文字コードをExcelで表示できるとは限らない
注意事項
- このコードは無保証です
- 細かな挙動は不明です。たとえば「巨大な文字列に適用した場合」「マッチ数が極端に多い場合」にどんな挙動をするのか明らかではありません。またエンコード・デコードの正当性(本当に正しいのかどうか)は十分に検証しておりません
- このコードはオブジェクト初期化時の計算コストが高いです。一度,生成したオブジェクトはなるべく使い回すようにしてください。とくにループの内部で何度もオブジェクトを生成してはいけません。スループット(処理効率)が低下します
コード(クラスモジュールHTMLEntity)
' require VBScript.RegExp, Scripting.Dictionary
Option Explicit
Private m_regex_encode As Object ' VBScript.RegExp
Private m_regex_decode As Object ' VBScript.RegExp
Private m_regex_decodehex As Object ' VBScript.RegExp
Private m_regex_decodedec As Object ' VBScript.RegExp
Private m_entity2char As Object ' Scripting.Dictionary
Private m_char2entity As Object ' Scripting.Dictionary
Private Function RegExp(pattern As String, Optional g As Boolean = False, _
Optional i As Boolean = False) As Object
Set RegExp = CreateObject("VBScript.RegExp")
With RegExp
.pattern = pattern
.Global = g
.ignorecase = i
End With
End Function
Private Sub Class_Initialize()
Dim t As String
Dim m As Object ' match
Dim name As String
Dim value As String
Set m_regex_encode = RegExp("([""&<>])", g:=True)
Set m_regex_decode = RegExp("&([\w#][\w\d]*);?", g:=True)
Set m_regex_decodehex = RegExp("#[xX]([0-9a-fA-F]{1,4})")
Set m_regex_decodedec = RegExp("#(\d{1,5})")
Set m_entity2char = CreateObject("Scripting.Dictionary")
Set m_char2entity = CreateObject("Scripting.Dictionary")
' ISO 8859-1
t = "" _
& "nbsp 160 iexcl 161 cent 162 pound 163 curren 164 " _
& "yen 165 brvbar 166 sect 167 uml 168 copy 169 " _
& "ordf 170 laquo 171 not 172 shy 173 reg 174 " _
& "macr 175 deg 176 plusmn 177 sup2 178 sup3 179 " _
& "acute 180 micro 181 para 182 middot 183 cedil 184 " _
& "sup1 185 ordm 186 raquo 187 frac14 188 frac12 189 " _
& "frac34 190 iquest 191 Agrave 192 Aacute 193 Acirc 194 " _
& "Atilde 195 Auml 196 Aring 197 AElig 198 Ccedil 199 " _
& "Egrave 200 Eacute 201 Ecirc 202 Euml 203 Igrave 204 " _
& "Iacute 205 Icirc 206 Iuml 207 ETH 208 Ntilde 209 " _
& "Ograve 210 Oacute 211 Ocirc 212 Otilde 213 Ouml 214 " _
& "times 215 Oslash 216 Ugrave 217 Uacute 218 Ucirc 219 " _
& "Uuml 220 Yacute 221 THORN 222 szlig 223 agrave 224 " _
& "aacute 225 acirc 226 atilde 227 auml 228 aring 229 " _
& "aelig 230 ccedil 231 egrave 232 eacute 233 ecirc 234 " _
& "euml 235 igrave 236 iacute 237 icirc 238 iuml 239 " _
& "eth 240 ntilde 241 ograve 242 oacute 243 ocirc 244 " _
& "otilde 245 ouml 246 divide 247 oslash 248 ugrave 249 " _
& "uacute 250 ucirc 251 uuml 252 yacute 253 thorn 254 " _
& "yuml 255"
' Mathematical, Greek and Symbolic (1/2)
t = t & " " _
& "fnof 402 Alpha 913 Beta 914 Gamma 915 " _
& "Delta 916 Epsilon 917 Zeta 918 Eta 919 Theta 920 " _
& "Iota 921 Kappa 922 Lambda 923 Mu 924 Nu 925 " _
& "Xi 926 Omicron 927 Pi 928 Rho 929 Sigma 931 " _
& "Tau 932 Upsilon 933 Phi 934 Chi 935 Psi 936 " _
& "Omega 937 alpha 945 beta 946 gamma 947 delta 948 " _
& "epsilon 949 zeta 950 eta 951 theta 952 iota 953 " _
& "kappa 954 lambda 955 mu 956 nu 957 xi 958 " _
& "omicron 959 pi 960 rho 961 sigmaf 962 sigma 963 " _
& "tau 964 upsilon 965 phi 966 chi 967 psi 968 " _
& "omega 969 thetasym 977 upsih 978 piv 982 bull 8226 " _
& "hellip 8230 prime 8242 Prime 8243 oline 8254 frasl 8260 " _
& "weierp 8472 image 8465 real 8476 trade 8482 alefsym 8501 " _
& "larr 8592 uarr 8593 rarr 8594 darr 8595 harr 8596 " _
& "crarr 8629 lArr 8656 uArr 8657 rArr 8658 dArr 8659 " _
& "hArr 8660 forall 8704 part 8706 exist 8707 empty 8709 " _
& "nabla 8711 isin 8712 notin 8713 ni 8715 prod 8719 " _
& "sum 8721 minus 8722 lowast 8727 radic 8730 prop 8733 " _
& "infin 8734 ang 8736 and 8743 or 8744 cap 8745 " _
& "cup 8746 int 8747 there4 8756 sim 8764 cong 8773 " _
& "asymp 8776 ne 8800 equiv 8801 le 8804 ge 8805 " _
& "sub 8834 sup 8835 nsub 8836 sube 8838 supe 8839 " _
& "oplus 8853 otimes 8855 perp 8869 sdot 8901 lceil 8968 "
' Mathematical, Greek and Symbolic (2/2)
t = t & " " _
& "loz 9674 spades 9824 clubs 9827 hearts 9829 diams 9830 " _
& "rceil 8969 lfloor 8970 rfloor 8971 lang 9001 rang 9002 "
' Special
t = t & " " _
& "quot 34 amp 38 lt 60 gt 62 OElig 338 " _
& "oelig 339 Scaron 352 scaron 353 Yuml 376 circ 710 " _
& "tilde 732 ensp 8194 emsp 8195 thinsp 8201 zwnj 8204 " _
& "zwj 8205 lrm 8206 rlm 8207 ndash 8211 mdash 8212 " _
& "lsquo 8216 rsquo 8217 sbquo 8218 ldquo 8220 rdquo 8221 " _
& "bdquo 8222 dagger 8224 Dagger 8225 permil 8240 lsaquo 8249 " _
& "rsaquo 8250 euro 8364"
For Each m In RegExp("([\w][\w\d]*)\s(\d+)\s?", g:=True).Execute(t)
name = m.submatches(0)
value = ChrW(m.submatches(1))
m_entity2char.Add name, value
m_char2entity.Add value, name
Next
End Sub
Public Function encode(s As String) As String
Dim p As Integer
Dim str As String
Dim m As Object ' match
p = 0
str = ""
For Each m In m_regex_encode.Execute(s)
If p < m.firstindex Then
str = str & Mid(s, p + 1, m.firstindex - p)
End If
str = str & "&" & m_char2entity.Item(m.submatches(0)) & ";"
p = m.firstindex + m.Length
Next
str = str & Mid(s, p + 1)
encode = str
End Function
Public Function decode(s As String) As String
Dim p As Integer
Dim str As String
Dim m As Object ' match
Dim c As String
p = 0
str = ""
For Each m In m_regex_decode.Execute(s)
c = m.submatches(0)
If p < m.firstindex Then
' 実体参照が現れる位置までの文字列をコピーする
str = str & Mid(s, p + 1, m.firstindex - p)
End If
If m_entity2char.Exists(c) Then
' 文字実体参照
str = str & m_entity2char.Item(c)
Else
With m_regex_decodehex.Execute(c)
If .Count Then
' 数値文字参照16進
str = str & Chr(val("&h" & .Item(0).submatches(0)))
Else
With m_regex_decodedec.Execute(c)
If .Count Then
' 数値文字参照10進
str = str & Chr(.Item(0).submatches(0))
Else
' 文字実体参照でも数値文字参照でもない
str = str & c
End If
End With
End If
End With
End If
p = m.firstindex + m.Length
Next
str = str & Mid(s, p + 1)
decode = str
End Function
'EOF
例
Dim o As New HTMLEntity
Debug.Print o.decode("&")
Debug.Print o.decode("&"<>")
Debug.Print o.decode("A&")
Debug.Print o.decode("A&B")
Debug.Print o.decode("&B")
Debug.Print o.decode("ABC")
Debug.Print o.decode("ABC")
Debug.Print o.decode("ABC") ' 実体参照を含まない
Debug.Print o.decode("†‡") '
Debug.Print o.decode("A&B") ' 末尾に「;」がない
Debug.Print o.decode("あいうえ") ' 日本語
Debug.Print o.decode("あ&い") ' 日本語
Debug.Print o.decode("あ&い") ' 末尾に「;」がない
Debug.Print
Debug.Print o.encode("&")
Debug.Print o.encode("&""<>")
Debug.Print o.encode("A&")
Debug.Print o.encode("A&B")
Debug.Print o.encode("&B")
Debug.Print o.encode("ABC")
例の実行結果
ほかの方法
おそらく専ら問題になるのはデコードだと思います。正規表現を使わない方法となると,実体参照の種類だけReplace関数を実行するしかないように思えます。性能面でかなり問題が出るのではないでしょうか。
BASICって,そもそも文字列処理が得意ではない気がするので,あまり無理をすると不幸になる可能性が大です。どこからか実体参照を扱うCOMを調達した方がよいでしょう。