Option Explicit '================================================================ ' DiagDom : 診断一式(環境 / DOM取得可否 / 画面構造ダンプ) ' ・画面内の文字は伏せ字化して出力(ラベル語だけ元のまま) ' 漢字→漢 ひらがな→あ カタカナ→ア 数字→9 英字→A,a ' 半角空白→_ 全角空白→□ 記号(-:()/,.など)はそのまま ' ・実行手順: 新システムの画面をEdgeで開いたまま「診断一式」を実行 ' ・対応: Excel 2010以降(VBA7) ' ・このモジュールの InitLabels / Mask / SafeAttr は DiagUia からも使います '================================================================ Private Const MAX_ROWS As Long = 30000 Private Const COLS As Long = 14 Private Const EXT_CHECK As Boolean = False 'True: PowerShell/デバッグポート確認(職場の監視に注意) #If VBA7 Then Private Declare PtrSafe Function EnumWindows Lib "user32" (ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As Long Private Declare PtrSafe Function GetWindowTextW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpString As LongPtr, ByVal nMaxCount As Long) As Long Private Declare PtrSafe Function GetClassNameW Lib "user32" (ByVal hWnd As LongPtr, ByVal lpClassName As LongPtr, ByVal nMaxCount As Long) As Long Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long #End If Private gData() As Variant Private gN As Long Private gLabels As Variant Private gWinList As String Private gChromeOther As Long Private gSum As Worksheet Private gSumR As Long Private gClsCnt As Object Private gIdDup As Object Private gIdN As Long Private gIdShort As Long Private gElN As Long Private gHidInput As Long Private gHidNone As Long Private gHidZero As Long Private gCmtN As Long Private gHidList As String Private gDocs As Long Private gLogPath As String Private gErrList As String Private gErrTotal As Long '--------------------------------------------------------------- Public Sub 診断一式() Dim kw As String, stamp As String Dim sh As Object, w As Object, doc As Object Dim i As Long, n As Long, nHtml As Long, nKey As Long, pg As Long Dim tn As String, ln As String Dim lines() As String, pr() As String Dim wsD As Worksheet, wsL As Worksheet StpReset Stp "診断開始" InitLabels kw = InputBox("対象画面のタイトルの一部を入力してください。" & vbLf & _ "空欄でOK(見つかったHTMLDocumentをすべて調べます)", "診断一式") stamp = Format(Now, "mmdd_hhnn") gN = 0: gDocs = 0: gElN = 0: gIdN = 0: gIdShort = 0 gHidInput = 0: gHidNone = 0: gHidZero = 0: gCmtN = 0: gHidList = "" ReDim gData(1 To MAX_ROWS, 1 To COLS) Stp "Scripting.Dictionary作成(class/id集計用)" Set gClsCnt = CreateObject("Scripting.Dictionary") Set gIdDup = CreateObject("Scripting.Dictionary") Stp "診断シート追加(サマリ)" Set gSum = ThisWorkbook.Worksheets.Add gSum.Name = "サマリ" & stamp gSum.Columns("A:C").NumberFormat = "@" gSumR = 1 SPut "■環境" SPut "診断日時", Format(Now, "yyyy/mm/dd hh:nn:ss") SPut "Excel / OS", Application.Version & " / " & Application.OperatingSystem #If Win64 Then SPut "VBA", "64bit" #Else SPut "VBA", "32bit" #End If SPut "対象タイトル指定", IIf(kw = "", "(空欄)", kw) If EXT_CHECK Then ExtCheck '--- Win32でEdge/IEウィンドウを列挙(IEモードでなくても画面名が分かる) gWinList = "": gChromeOther = 0 Stp "Win32 EnumWindows(user32)" EnumWindows AddressOf EnumProc, 0 SPut "■Edge/IEウィンドウ(Win32列挙)" SPut "クラス", "画面名" lines = Split(gWinList, vbLf) For i = 0 To UBound(lines) If Len(lines(i)) > 0 Then pr = Split(lines(i), vbTab) SPut pr(0), pr(1) End If Next SPut "Edge表記なしのChrome系ウィンドウ数", CStr(gChromeOther) '--- Shell.Application: IEモードのウィンドウならHTMLDocumentとして見える SPut "■Shell.Applicationのウィンドウ(IEモード判定)" SPut "種類(TypeName)", "画面名", "URL(?より前)" Stp "Shell.Application作成" On Error Resume Next Set sh = CreateObject("Shell.Application") ErrNote "CreateObject(Shell.Application)" n = sh.Windows.Count ErrNote "Shell.Windows.Count" Stp "Shell.Windowsを列挙(" & n & "件)" For i = 0 To n - 1 Set w = Nothing Set w = sh.Windows.Item(i) ErrNote "Shell.Windows.Item" If Not w Is Nothing Then tn = "": ln = "" tn = TypeName(w.Document) ErrNote "w.Document" ln = w.LocationName ErrNote "w.LocationName" SPut tn, ln, Split(w.LocationURL & "?", "?")(0) ErrNote "w.LocationURL" If tn = "HTMLDocument" Then nHtml = nHtml + 1 If kw = "" Or InStr(ln, kw) > 0 Then Set doc = Nothing Set doc = w.Document ErrNote "Set doc = w.Document" If Not doc Is Nothing Then Stp "DOM取得開始 W" & nHtml DumpDoc doc, "W" & nHtml, 0 Stp "DOM取得完了 W" & nHtml & " (累計行数=" & gN & ")" End If End If End If End If Next On Error GoTo 0 SPut "HTMLDocument件数", CStr(nHtml) If nHtml = 0 Then SPut "判定", "HTMLDocumentなし = IEモード方式のDOM取得は不可", "→ DiagUia の UIA診断 を実行" Else SPut "判定", "HTMLDocumentあり = DOM取得方式が使える可能性", "" End If Stp "統計集計(サマリ)" SumTail Stp "ラベル周辺シート作成" Set wsL = ThisWorkbook.Worksheets.Add wsL.Name = "ラベル周辺" & stamp WriteLabelCtx wsL Stp "DOMシート書き出し(" & gN & "行)" Set wsD = ThisWorkbook.Worksheets.Add wsD.Name = "DOM" & stamp nKey = WriteDom(wsD) '--- 書式・印刷設定 Stp "書式・印刷設定" FmtSheet gSum, 9, False, "" SetWidths gSum, Array(34, 60, 40) gSum.Columns("B:C").WrapText = True gSum.UsedRange.Rows.AutoFit FmtSheet wsL, 8, True, "$1:$1" SetWidths wsL, Array(9, 6, 4, 4, 9, 22, 26, 9, 46, 40) FmtSheet wsD, 8, True, "$1:$1" SetWidths wsD, Array(6, 4, 4, 9, 22, 28, 12, 7, 12, 46, 6, 38, 40, 5) If gN > 0 Then On Error Resume Next wsD.PageSetup.PrintArea = wsD.Range("A1").Resize(gN + 1, 12).Address On Error GoTo 0 End If gSum.Activate pg = nKey \ 45 + 1 If MsgBox("診断シートができました。印刷しますか?" & vbLf & _ "・サマリ / ラベル周辺 / DOM(要点のみ 約" & pg & "ページ)" & vbLf & _ "※個人情報は伏せ字化していますが、印刷後に目視で確認してください。", _ vbYesNo + vbQuestion, "診断一式") = vbYes Then Stp "印刷(サマリ)": gSum.PrintOut Stp "印刷(ラベル周辺)": wsL.PrintOut Stp "印刷(DOM)": wsD.PrintOut End If Stp "完了" Application.StatusBar = False End Sub '--------------------------------------------------------------- Public Sub StpReset() gLogPath = "" gErrList = "" gErrTotal = 0 End Sub Public Sub Stp(ByVal s As String) '各ステップの「開始」を記録(途中で止まっても最後の行で場所が分かる) Dim f As Integer If Len(gLogPath) = 0 Then gLogPath = Environ("TEMP") & "\diag_step_" & Format(Now, "mmdd_hhnn") & ".log" Application.StatusBar = "診断: " & s On Error Resume Next f = FreeFile Open gLogPath For Append As #f Print #f, Format(Now, "hh:nn:ss") & " " & s Close #f End Sub Public Sub ErrNote(ByVal stepName As String) '直前の読み取りで起きたエラーを記録して続行 Dim key As String If Err.Number = 0 Then Exit Sub key = stepName & " / Err" & Err.Number & ": " & Left$(Err.Description, 60) gErrTotal = gErrTotal + 1 If InStr(gErrList, key) = 0 And Len(gErrList) < 3000 Then gErrList = gErrList & key & vbLf Stp "NG " & key End If Err.Clear End Sub Public Function ErrSummary() As String ErrSummary = "エラー総数=" & gErrTotal & vbLf & gErrList & "ログ=" & gLogPath End Function Public Sub InitLabels() '位置の手がかりとして伏せ字にしない語(長い語を先に) gLabels = Array("郵便番号", "都道府県", "市区町村", "建物名", "会社名", "法人名", _ "代表者", "担当者", "フリガナ", "氏名", "名前", "住所", "電話", _ "部署", "役職", "宛名", "送付先", "名義", "本人", "個人", "法人", _ "御中", "様", "〒") End Sub Public Function Mask(ByVal s As String) As String Dim i As Long, c As Long, ch As String, out As String s = Replace(Replace(Replace(s, vbCr, " "), vbLf, " "), vbTab, " ") For i = 0 To UBound(gLabels) s = Replace(s, gLabels(i), ChrW(&HE000& + i)) Next For i = 1 To Len(s) ch = Mid$(s, i, 1) c = AscW(ch): If c < 0 Then c = c + 65536 If c >= &HE000& And c <= &HE0FF& Then out = out & ch ElseIf c = 32 Or c = 160 Then out = out & "_" ElseIf c >= 48 And c <= 57 Then out = out & "9" ElseIf c >= 65 And c <= 90 Then out = out & "A" ElseIf c >= 97 And c <= 122 Then out = out & "a" ElseIf c < 128 Then out = out & ch Else out = out & JpMap(c, ch, False) End If Next For i = 0 To UBound(gLabels) out = Replace(out, ChrW(&HE000& + i), gLabels(i)) Next Mask = out End Function Public Function SafeAttr(ByVal s As String) As String 'id/class/属性値用: 日本語は伏せ字、5桁以上の数字列は桁数だけ残す Dim i As Long, c As Long, ch As String, out As String, dg As String For i = 1 To Len(s) ch = Mid$(s, i, 1) c = AscW(ch): If c < 0 Then c = c + 65536 If c >= 48 And c <= 57 Then dg = dg & ch Else If Len(dg) >= 5 Then out = out & "[数字" & Len(dg) & "桁]" Else out = out & dg dg = "" If c > 127 Then out = out & JpMap(c, ch, True) Else out = out & ch End If Next If Len(dg) >= 5 Then out = out & "[数字" & Len(dg) & "桁]" Else out = out & dg SafeAttr = out End Function Private Function JpMap(ByVal c As Long, ByVal ch As String, ByVal keepOther As Boolean) As String Select Case c Case &HFF10& To &HFF19&: JpMap = "9" Case &HFF21& To &HFF3A&: JpMap = "A" Case &HFF41& To &HFF5A&: JpMap = "a" Case &H3000&: JpMap = "□" Case &H3041& To &H309F&: JpMap = "あ" Case &H30FB&: JpMap = ch Case &H30A0& To &H30FF&, &HFF66& To &HFF9F&: JpMap = "ア" Case &H3400& To &H9FFF&, &H3005&, &H3006&: JpMap = "漢" Case &HFF0D&, &H2010& To &H2015&, &H2212&, &HFF1A&, &HFF08&, &HFF09&, _ &H3012&, &H3001&, &H3002&, &HFF0F&, &HFF0C&, &HFF0E&: JpMap = ch Case Else If keepOther Then JpMap = ch Else JpMap = "●" End Select End Function Private Function CodeLike(ByVal s As String) As Boolean 'c1 / a23 / ab123 のような「英字1-2文字+数字」型か Dim i As Long If Len(s) = 0 Or Len(s) > 6 Then Exit Function i = Len(s) Do While i > 0 If Mid$(s, i, 1) Like "[0-9]" Then i = i - 1 Else Exit Do Loop CodeLike = (Len(s) - i >= 1 And i <= 2) End Function '--------------------------------------------------------------- #If VBA7 Then Private Function EnumProc(ByVal hWnd As LongPtr, ByVal lParam As LongPtr) As Long Dim cls As String, ttl As String, n As Long EnumProc = 1 If IsWindowVisible(hWnd) = 0 Then Exit Function cls = String$(256, vbNullChar) n = GetClassNameW(hWnd, StrPtr(cls), 256) cls = Left$(cls, n) If cls = "Chrome_WidgetWin_1" Or cls = "IEFrame" Then ttl = String$(512, vbNullChar) n = GetWindowTextW(hWnd, StrPtr(ttl), 512) ttl = Left$(ttl, n) If Len(ttl) > 0 Then If cls = "IEFrame" Or InStr(ttl, "Edge") > 0 Then gWinList = gWinList & cls & vbTab & ttl & vbLf Else gChromeOther = gChromeOther + 1 End If End If End If End Function #End If Private Sub SPut(ByVal a As String, Optional ByVal b As String = "", Optional ByVal c As String = "") gSum.Cells(gSumR, 1).Value = a gSum.Cells(gSumR, 2).Value = b gSum.Cells(gSumR, 3).Value = c If Left$(a, 1) = "■" Then gSum.Cells(gSumR, 1).Font.Bold = True gSumR = gSumR + 1 End Sub '--------------------------------------------------------------- Private Sub DumpDoc(ByVal doc As Object, ByVal lbl As String, ByVal level As Long) Dim frames As New Collection Dim t As String, u As String, dm As String, cm As String, cs As String, rs As String Dim nIf As Long, nFr As Long, startN As Long, i As Long, myNo As Long Dim fr As Object, subDoc As Object, fn As String, fs As String On Error Resume Next gDocs = gDocs + 1 myNo = gDocs startN = gN t = doc.Title ErrNote "doc.Title" u = Split(doc.URL & "?", "?")(0) ErrNote "doc.URL" dm = doc.documentMode & "" ErrNote "doc.documentMode" cm = doc.compatMode & "" ErrNote "doc.compatMode" cs = doc.CharSet & "" rs = doc.readyState & "" ErrNote "doc.CharSet/readyState" nIf = doc.getElementsByTagName("iframe").Length nFr = doc.getElementsByTagName("frame").Length ErrNote "doc.getElementsByTagName" SPut "■文書 " & lbl SPut "タイトル", t SPut "URL(?より前)", u SPut "documentMode / compatMode", dm & " / " & cm, "charset=" & cs & " readyState=" & rs SPut "iframe数 / frame数", nIf & " / " & nFr Walk doc.documentElement, 0, "html", myNo, frames SPut "取得要素行数", CStr(gN - startN) If level < 4 Then For i = 1 To frames.Count Set fr = frames(i) fn = "": fs = "" fn = SafeAttr(fr.getAttribute("name") & "") fs = SafeAttr(Split(fr.getAttribute("src") & "?", "?")(0)) Set subDoc = Nothing Set subDoc = fr.contentWindow.Document ErrNote "frame.contentWindow.Document(別ドメイン等で取得不可)" If subDoc Is Nothing Then SPut " frame" & i & " 中身を取得できません", "name=" & fn, "src=" & fs Else SPut " frame" & i & " を取得", "name=" & fn, "src=" & fs DumpDoc subDoc, lbl & "-F" & i, level + 1 End If Next End If End Sub Private Sub Walk(ByVal el As Object, ByVal depth As Long, ByVal path As String, _ ByVal docNo As Long, ByVal frames As Collection) Dim kids As Object, nd As Object, cnt As Long, i As Long, k As Long, tp As Long Dim own As String, cmt As String, tg As String, parts() As String, p As Long If gN >= MAX_ROWS Then Exit Sub On Error Resume Next Set kids = el.childNodes cnt = kids.Length ErrNote "childNodes/Length" For i = 0 To cnt - 1 Set nd = Nothing Set nd = kids.Item(i) If Not nd Is Nothing Then tp = 0 tp = nd.nodeType ErrNote "nodeType" If tp = 3 Then own = own & " " & nd.nodeValue ElseIf tp = 8 Then cmt = cmt & Chr(1) & Left$(nd.nodeValue & "", 300) End If End If Next tg = "" tg = UCase$(el.tagName & "") ErrNote "tagName" AddRow el, depth, path, docNo, own If tg = "IFRAME" Or tg = "FRAME" Then frames.Add el If Len(cmt) > 0 Then parts = Split(cmt, Chr(1)) For p = 1 To UBound(parts) AddCmt parts(p), depth, path, docNo Next End If If tg = "SCRIPT" Or tg = "STYLE" Or tg = "SELECT" Then Exit Sub k = 0 For i = 0 To cnt - 1 Set nd = Nothing Set nd = kids.Item(i) If Not nd Is Nothing Then tp = 0 tp = nd.nodeType If tp = 1 Then k = k + 1 Walk nd, depth + 1, path & ">" & LCase$(nd.tagName & "") & "[" & k & "]", docNo, frames End If End If Next End Sub Private Sub AddRow(ByVal el As Object, ByVal depth As Long, ByVal path As String, _ ByVal docNo As Long, ByVal own As String) Dim tg As String, id As String, cl As String, nm As String, ty As String Dim hid As String, oth As String, txt As String, v As String Dim dispS As String, visS As String, an As String, av As String Dim a As Object, i As Long, na As Long, si As Long, ow As Long, oh As Long Dim spec As Boolean, parts() As String, p As Variant If gN >= MAX_ROWS Then Exit Sub On Error Resume Next tg = UCase$(el.tagName & "") ErrNote "el.tagName" id = el.ID & "" ErrNote "el.ID" cl = el.className & "" ErrNote "el.className" nm = el.getAttribute("name") & "" ty = LCase$(el.getAttribute("type") & "") ErrNote "el.getAttribute" dispS = el.currentStyle.display & "" visS = el.currentStyle.visibility & "" ErrNote "el.currentStyle" ow = -1: ow = el.offsetWidth oh = -1: oh = el.offsetHeight ErrNote "el.offsetWidth/Height" If ty = "hidden" Then hid = "hiddenInput" gHidInput = gHidInput + 1 v = "": v = el.Value & "" If Len(gHidList) < 4000 Then gHidList = gHidList & SafeAttr(nm) & "/" & SafeAttr(id) & "=" & Left$(Mask(v), 30) & vbLf End If End If If dispS = "none" Then hid = Trim$(hid & " none") gHidNone = gHidNone + 1 End If If visS = "hidden" Then hid = Trim$(hid & " vhidden") If ow = 0 And oh = 0 Then hid = Trim$(hid & " size0") gHidZero = gHidZero + 1 End If own = Replace(Replace(Replace(Replace(own, vbCr, " "), vbLf, " "), vbTab, " "), ChrW(160), " ") Do While InStr(own, " ") > 0 own = Replace(own, " ", " ") Loop txt = Trim$(own) If tg = "SCRIPT" Or tg = "STYLE" Then txt = "[" & Len(txt) & "文字]" Else If Len(txt) > 0 Then txt = Left$(Mask(txt), 200) If tg = "INPUT" Or tg = "TEXTAREA" Then v = "": v = el.Value & "" If Len(v) > 0 And ty <> "hidden" Then txt = Trim$("[v]" & Left$(Mask(v), 120) & " " & txt) ElseIf tg = "SELECT" Then si = -1: si = el.selectedIndex oth = "options=" & el.Options.Length & "; " If si >= 0 Then v = "": v = el.Options(si).Text & "" txt = Trim$("[sel]" & Left$(Mask(v), 80) & " " & txt) End If End If End If na = -1: na = el.Attributes.Length ErrNote "el.Attributes" For i = 0 To na - 1 Set a = Nothing Set a = el.Attributes.Item(i) If Not a Is Nothing Then spec = False: spec = a.specified If spec Then an = LCase$(a.nodeName & "") av = a.nodeValue & "" Select Case an Case "id", "class", "name", "type", "style", "value" Case "href", "src" oth = oth & an & "=" & Left$(SafeAttr(Split(av & "?", "?")(0)), 50) & "; " Case "title", "alt", "placeholder", "aria-label" oth = oth & an & "=" & Left$(Mask(av), 40) & "; " Case Else oth = oth & an & "=" & Left$(SafeAttr(av), 40) & "; " End Select End If End If Next gN = gN + 1 gData(gN, 1) = gN gData(gN, 2) = docNo gData(gN, 3) = depth gData(gN, 4) = tg gData(gN, 5) = SafeAttr(id) gData(gN, 6) = SafeAttr(cl) gData(gN, 7) = SafeAttr(nm) gData(gN, 8) = ty gData(gN, 9) = hid gData(gN, 10) = txt gData(gN, 11) = Len(own) gData(gN, 12) = oth gData(gN, 13) = path gElN = gElN + 1 If Len(id) > 0 Then gIdN = gIdN + 1 If gIdDup.Exists(id) Then gIdDup(id) = gIdDup(id) + 1 Else gIdDup.Add id, 1 End If If CodeLike(id) Then gIdShort = gIdShort + 1 End If If Len(cl) > 0 Then parts = Split(Trim$(cl), " ") For Each p In parts If Len(p) > 0 Then If gClsCnt.Exists(p) Then gClsCnt(p) = gClsCnt(p) + 1 Else gClsCnt.Add p, 1 End If End If Next End If End Sub Private Sub AddCmt(ByVal s As String, ByVal depth As Long, ByVal path As String, ByVal docNo As Long) If gN >= MAX_ROWS Then Exit Sub gN = gN + 1 gCmtN = gCmtN + 1 gData(gN, 1) = gN gData(gN, 2) = docNo gData(gN, 3) = depth + 1 gData(gN, 4) = "#COMMENT" gData(gN, 10) = Left$(Mask(s), 200) gData(gN, 11) = Len(s) gData(gN, 13) = path & ">#comment" End Sub Private Function IsKey(ByVal i As Long) As Boolean Dim tg As String tg = gData(i, 4) IsKey = (Len(gData(i, 10)) > 0) Or (Len(gData(i, 5)) > 0) Or _ tg = "INPUT" Or tg = "SELECT" Or tg = "TEXTAREA" Or tg = "IFRAME" Or _ tg = "FRAME" Or tg = "BUTTON" Or tg = "LABEL" Or tg = "TABLE" Or _ tg = "TH" Or tg = "#COMMENT" End Function '--------------------------------------------------------------- Private Sub SumTail() Dim k As Variant, best As String, bc As Long, i As Long, dupN As Long Dim lines() As String SPut "■DOM統計(全文書の合計)" SPut "文書数(frame含む)", CStr(gDocs) SPut "取得した要素行数", CStr(gN), IIf(gN >= MAX_ROWS, "上限到達(途中まで)", "") SPut "id付き要素数", CStr(gIdN), "idの種類数=" & gIdDup.Count For Each k In gIdDup.Keys If gIdDup(k) > 1 Then dupN = dupN + 1 Next SPut "重複しているid数", CStr(dupN) SPut "idが英字1-2文字+数字型(c1,a23風)", CStr(gIdShort), "id付き要素のうちの件数" SPut "classの種類数", CStr(gClsCnt.Count) SPut "非表示 hiddenInput / display:none / サイズ0", gHidInput & " / " & gHidNone & " / " & gHidZero SPut "HTMLコメント数", CStr(gCmtN) SPut "■class使用頻度 上位20", "出現数" For i = 1 To 20 best = "": bc = 0 For Each k In gClsCnt.Keys If gClsCnt(k) > bc Then bc = gClsCnt(k) best = CStr(k) End If Next If bc = 0 Then Exit For SPut SafeAttr(best), CStr(bc) gClsCnt(best) = -bc Next If Len(gHidList) > 0 Then SPut "■hidden input (name/id=値の伏せ字)" lines = Split(gHidList, vbLf) For i = 0 To UBound(lines) If Len(lines(i)) > 0 And i < 40 Then SPut lines(i) Next End If SPut "■読み取りエラー記録(種類ごと。Err番号と内容)" SPut "エラー総数", CStr(gErrTotal), "要素の種類により出る正常な失敗も含む" If Len(gErrList) > 0 Then lines = Split(gErrList, vbLf) For i = 0 To UBound(lines) If Len(lines(i)) > 0 Then SPut lines(i) Next End If SPut "ステップログ(最後の行=最後に開始した処理)", gLogPath End Sub Private Sub WriteLabelCtx(ByVal ws As Worksheet) Dim idx() As Long, m As Long, i As Long, pos As Long, j As Long, r As Long Dim t As String, lb As Variant, hitLb As String, shown As Object Dim a As Long, b As Long, kind As String ws.Range("A:J").NumberFormat = "@" ws.Range("A1:J1").Value = Array("区分", "連番", "文書", "深さ", "タグ", "id", "class", "非表示", "テキスト(伏せ字)", "パス") r = 2 ReDim idx(1 To gN + 1) For i = 1 To gN If Len(gData(i, 10)) > 0 Then Select Case gData(i, 4) Case "#COMMENT", "SCRIPT", "STYLE" Case Else m = m + 1 idx(m) = i End Select End If Next Set shown = CreateObject("Scripting.Dictionary") For pos = 1 To m t = gData(idx(pos), 10) hitLb = "" For Each lb In gLabels If InStr(t, lb) > 0 Then hitLb = lb Exit For End If Next If hitLb <> "" Then If Not shown.Exists(hitLb) Then shown.Add hitLb, 0 If shown(hitLb) < 3 Then shown(hitLb) = shown(hitLb) + 1 a = pos - 1: If a < 1 Then a = 1 b = pos + 3: If b > m Then b = m For j = a To b If j < pos Then kind = "前" ElseIf j = pos Then kind = "【" & hitLb & "】" Else kind = "次" & (j - pos) End If PutRow ws, r, kind, idx(j) r = r + 1 Next r = r + 1 End If End If Next If r = 2 Then ws.Range("A2").Value = "ラベル語を含む行が見つかりませんでした" End Sub Private Sub PutRow(ByVal ws As Worksheet, ByVal r As Long, ByVal kind As String, ByVal i As Long) ws.Cells(r, 1).Value = kind ws.Cells(r, 2).Value = gData(i, 1) ws.Cells(r, 3).Value = gData(i, 2) ws.Cells(r, 4).Value = gData(i, 3) ws.Cells(r, 5).Value = gData(i, 4) ws.Cells(r, 6).Value = gData(i, 5) ws.Cells(r, 7).Value = gData(i, 6) ws.Cells(r, 8).Value = gData(i, 9) ws.Cells(r, 9).Value = gData(i, 10) ws.Cells(r, 10).Value = gData(i, 13) End Sub Private Function WriteDom(ByVal ws As Worksheet) As Long Dim tmp() As Variant, i As Long, c As Long, nKey As Long If gN = 0 Then ws.Range("A1").Value = "要素なし(HTMLDocumentが取得できませんでした)" Exit Function End If ReDim tmp(1 To gN, 1 To COLS) For i = 1 To gN For c = 1 To 13 tmp(i, c) = gData(i, c) Next If IsKey(i) Then tmp(i, 14) = 1 nKey = nKey + 1 Else tmp(i, 14) = 0 End If Next ws.Range("D:J,L:M").NumberFormat = "@" ws.Range("A1").Resize(1, COLS).Value = Array("連番", "文書", "深さ", "タグ", "id", "class", "name", "type", _ "非表示", "直下テキスト(伏せ字)", "直下文字数", "その他属性", "パス", "要点") ws.Range("A2").Resize(gN, COLS).Value = tmp ws.Range("A1").Resize(gN + 1, COLS).AutoFilter Field:=14, Criteria1:="1" WriteDom = nKey End Function Private Sub FmtSheet(ByVal ws As Worksheet, ByVal fs As Double, ByVal land As Boolean, ByVal titleRows As String) On Error Resume Next ws.Cells.Font.Size = fs With ws.PageSetup If land Then .Orientation = xlLandscape Else .Orientation = xlPortrait End If .Zoom = False .FitToPagesWide = 1 .FitToPagesTall = False .LeftHeader = "&D &T" .CenterFooter = "&P / &N" If Len(titleRows) > 0 Then .PrintTitleRows = titleRows End With End Sub Private Sub SetWidths(ByVal ws As Worksheet, ByVal w As Variant) Dim i As Long For i = 0 To UBound(w) ws.Columns(i + 1).ColumnWidth = w(i) Next End Sub '--------------------------------------------------------------- Private Sub ExtCheck() 'PowerShell起動可否とデバッグポート(9222)の応答確認 Dim sh As Object, ex As Object, t As Single, s As String, http As Object SPut "■拡張確認(PowerShell / デバッグポート)" On Error Resume Next Set sh = CreateObject("WScript.Shell") Set ex = sh.Exec("powershell -NoProfile -NonInteractive -Command ""$PSVersionTable.PSVersion.ToString(); $ExecutionContext.SessionState.LanguageMode""") If Err.Number <> 0 Then SPut "PowerShell", "起動不可", Err.Description Err.Clear Else t = Timer Do While ex.Status = 0 And Timer - t < 15 DoEvents Loop s = Replace(ex.StdOut.ReadAll, vbCrLf, " / ") SPut "PowerShell(版 / 言語モード)", s End If Err.Clear Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0") http.setTimeouts 2000, 2000, 2000, 2000 http.Open "GET", "http://127.0.0.1:9222/json/version", False http.send If Err.Number <> 0 Then SPut "デバッグポート9222", "応答なし(Edgeは通常起動)" Err.Clear Else SPut "デバッグポート9222", http.Status & " " & Left$(http.responseText, 100) End If End Sub
————————————————————————————————————————————————————————————
Option Explicit '================================================================ ' DiagUia : UI Automation診断(IEモードでDOMが取れない場合用) ' 事前準備: ' 1) VBEの「ツール > 参照設定」で「UIAutomationClient」にチェック ' (Windows標準のライブラリ。インストール不要) ' 2) DiagDom も標準モジュールとして貼り付けておく ' (InitLabels / Mask / SafeAttr を共用します) ' 実行: 新システムの画面をEdgeで開いたまま「UIA診断」を実行 ' 出力: 文字は伏せ字化(ラベル語のみ元のまま)。画面全体ではなく ' Edge内の画面(Document)以下だけを調べます。 '================================================================ Private Const MAX_N As Long = 20000 Public Sub UIA診断() Dim uia As New CUIAutomation Dim root As IUIAutomationElement, wins As IUIAutomationElementArray Dim tgt As IUIAutomationElement, docEl As IUIAutomationElement, el As IUIAutomationElement Dim arr As IUIAutomationElementArray, cond As IUIAutomationCondition Dim vp As IUIAutomationValuePattern, rc As tagRECT Dim kw As String, nm As String, ttl As String, v As String, tl As String, tys As String Dim i As Long, n As Long, tries As Long, r As Long, nKey As Long, nId As Long, ct As Long, c As Long Dim ws As Worksheet, out() As Variant, tmp() As Variant, tyCnt As Object, k As Variant Dim pg As Long StpReset Stp "UIA診断開始" InitLabels kw = InputBox("対象Edge画面のタイトルの一部を入力してください。" & vbLf & _ "空欄の場合は最初に見つかったEdgeウィンドウを調べます。", "UIA診断") '--- Edgeウィンドウを探す Stp "UIA: CUIAutomation生成とルート取得" Set root = uia.GetRootElement Stp "UIA: トップレベルウィンドウ列挙(FindAll)" Set wins = root.FindAll(2, uia.CreateTrueCondition) 'TreeScope_Children Stp "UIA: Edgeウィンドウ探索(" & wins.Length & "件中)" For i = 0 To wins.Length - 1 Set el = wins.GetElement(i) If el.CurrentClassName = "Chrome_WidgetWin_1" Then nm = el.CurrentName If InStr(nm, "Edge") > 0 And (kw = "" Or InStr(nm, kw) > 0) Then Set tgt = el ttl = nm Exit For End If End If Next If tgt Is Nothing Then Stp "NG: Edgeウィンドウが見つからない" Application.StatusBar = False MsgBox "Edgeウィンドウが見つかりません。", vbExclamation Exit Sub End If '--- 画面内容(Document)を探す(Edgeは最初の接続で準備するため数回リトライ) Stp "UIA: Document要素を探索(FindFirst)" Set cond = uia.CreatePropertyCondition(30003, 50030) 'ControlType = Document For tries = 1 To 5 Set docEl = Nothing On Error Resume Next Set docEl = tgt.FindFirst(4, cond) 'TreeScope_Descendants On Error GoTo 0 If Not docEl Is Nothing Then Exit For Application.Wait Now + TimeSerial(0, 0, 2) Next If docEl Is Nothing Then Stp "NG: Document要素が見つからない" Application.StatusBar = False MsgBox "画面内容(Document)を取得できません。", vbExclamation Exit Sub End If '--- 全要素を取得 Stp "UIA: 全要素取得(FindAll。時間がかかる場合あり)" Application.StatusBar = "UIA要素を取得中…(数十秒かかることがあります)" Set arr = docEl.FindAll(4, uia.CreateTrueCondition) n = arr.Length If n > MAX_N Then n = MAX_N ReDim out(1 To IIf(n > 0, n, 1), 1 To 13) Set tyCnt = CreateObject("Scripting.Dictionary") On Error Resume Next For i = 0 To n - 1 Set el = Nothing Set el = arr.GetElement(i) ErrNote "UIA GetElement" If Not el Is Nothing Then r = r + 1 tl = "": tl = el.CurrentLocalizedControlType ct = 0: ct = el.CurrentControlType ErrNote "UIA LocalizedControlType/ControlType" out(r, 1) = r out(r, 2) = tl out(r, 3) = ct out(r, 4) = SafeAttr(el.CurrentAutomationId & "") out(r, 5) = SafeAttr(el.CurrentClassName & "") ErrNote "UIA AutomationId/ClassName" nm = "": nm = el.CurrentName ErrNote "UIA Name" If Len(nm) > 0 Then out(r, 6) = Left$(Mask(nm), 200) Set vp = Nothing Set vp = el.GetCurrentPattern(10002) 'ValuePattern v = "" If Not vp Is Nothing Then v = vp.CurrentValue ErrNote "UIA ValuePattern" If Len(v) > 0 Then out(r, 7) = Left$(Mask(v), 120) If el.CurrentIsOffscreen <> 0 Then out(r, 8) = "画面外" rc = el.CurrentBoundingRectangle ErrNote "UIA BoundingRectangle" out(r, 9) = rc.Left out(r, 10) = rc.Top out(r, 11) = rc.Right - rc.Left out(r, 12) = rc.Bottom - rc.Top If Len(out(r, 4)) > 0 Then nId = nId + 1 If tyCnt.Exists(tl) Then tyCnt(tl) = tyCnt(tl) + 1 Else tyCnt.Add tl, 1 End If If Len(out(r, 6)) > 0 Or Len(out(r, 7)) > 0 Or Len(out(r, 4)) > 0 Or _ ct = 50004 Or ct = 50003 Or ct = 50000 Or ct = 50005 Or ct = 50002 Or ct = 50013 Then out(r, 13) = 1 nKey = nKey + 1 Else out(r, 13) = 0 End If End If If i Mod 200 = 0 Then Application.StatusBar = "UIA " & i & " / " & n DoEvents End If Next On Error GoTo 0 Application.StatusBar = False '--- シートへ出力 Stp "UIA: シート出力(" & r & "件)" Set ws = ThisWorkbook.Worksheets.Add ws.Name = "UIA" & Format(Now, "mmdd_hhnn") ws.Cells.NumberFormat = "@" ws.Range("A1").Value = "UIA診断 " & Format(Now, "yyyy/mm/dd hh:nn") ws.Range("A2").Value = "Edge画面名" ws.Range("B2").Value = ttl ws.Range("A3").Value = "取得要素数" ws.Range("B3").Value = CStr(r) & IIf(arr.Length > MAX_N, " (上限で打切り)", "") ws.Range("A4").Value = "AutomationIdあり" ws.Range("B4").Value = CStr(nId) For Each k In tyCnt.Keys tys = tys & k & "=" & tyCnt(k) & " / " Next ws.Range("A5").Value = "種類別件数" ws.Range("B5").Value = tys ws.Range("A6").Value = "読取エラー/ログ" ws.Range("B6").Value = ErrSummary() ws.Range("B6").WrapText = True ws.Range("A7").Resize(1, 13).Value = Array("連番", "種類", "型番号", "AutomationId", "ClassName", _ "名前(伏せ字)", "値(伏せ字)", "画面外", "左", "上", "幅", "高さ", "要点") If r > 0 Then ReDim tmp(1 To r, 1 To 13) For i = 1 To r For c = 1 To 13 tmp(i, c) = out(i, c) Next Next ws.Range("A8").Resize(r, 13).Value = tmp ws.Range("A7").Resize(r + 1, 13).AutoFilter Field:=13, Criteria1:="1" End If ws.Cells.Font.Size = 8 ws.Columns("B5").WrapText = True ws.Columns(1).ColumnWidth = 18 ws.Columns(2).ColumnWidth = 14 ws.Columns(3).ColumnWidth = 7 ws.Columns(4).ColumnWidth = 22 ws.Columns(5).ColumnWidth = 24 ws.Columns(6).ColumnWidth = 44 ws.Columns(7).ColumnWidth = 30 ws.Columns(8).ColumnWidth = 7 For c = 9 To 13 ws.Columns(c).ColumnWidth = 6 Next On Error Resume Next With ws.PageSetup .Orientation = xlLandscape .Zoom = False .FitToPagesWide = 1 .FitToPagesTall = False .LeftHeader = "&D &T" .CenterFooter = "&P / &N" .PrintTitleRows = "$7:$7" .PrintArea = ws.Range("A1").Resize(r + 7, 12).Address End With On Error GoTo 0 ws.Activate Stp "UIA完了(印刷確認)" pg = nKey \ 45 + 1 If r < 20 Then MsgBox "取得できた要素が少ない(" & r & "件)です。" & vbLf & _ "Edgeは最初のUIA接続で準備するため、もう一度実行すると取れることがあります。", vbInformation End If If MsgBox("UIAシートができました。印刷しますか?(要点のみ 約" & pg & "ページ)" & vbLf & _ "※個人情報は伏せ字化していますが、印刷後に目視で確認してください。", _ vbYesNo + vbQuestion, "UIA診断") = vbYes Then Stp "印刷(UIA)" ws.PrintOut End If Stp "完了" Application.StatusBar = False End Sub