変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

Excel

「EXCEL」Lambda関数

謎が多いLambda関数。

外部からの参照時にパラメータを貰い、Lambda関数の処理に引き渡せる点が光っている。

=LAMBDA(a,b,SQRT(a*b))

と書くと

#CALC!

になってしまう。

これはパラメータを指定していないためで、パラメータを追加すると

=LAMBDA(a,b,SQRT(a*b))(10,10) ⇒ 10

となるけど、こうなってしまっては使い道が全くない。

そこで、セル名の参照範囲が=で始まるのを利用し、(10,10)抜きでLAMBDA式を適当なセル名で登録すれば・・・

セル名:あああ
参照範囲:=LAMBDA(a,b,SQRT(a*b))

どこかのセルの式:あああ(10,10)⇒10

と云う感じでVBAマクロを使わなくてもユーザ関数(今風にはカスタム関数と云うらしい)を作ることができる。

但し、処理が1行に収まる様に纏められるならないが

そこはLET関数を使って、1行に纏められるなら

ワークシートに散乱している=LET(・・・な計算式を一纏めにできるので

後々の修正もセル名の計算式を治すだけでうまくいく(かもしれない

好感度やランキングの様なその時々で評価式を一斉に変えないといけない案件には非常に有効な気がする。

尚、このLAMBDA関数はループができるらしい。

式:=REDUCE(0,A1:C2, LAMBDA(a,b,a+b^2))

ちょっと見、訳が判らないけど、A1からC2の2乗値を合算する式となっている

LAMBDAの第1パラメータは多分、初期値か前回のLAMBDA関数の計算結果

LAMBDAの第2パラメータは計算して欲しいセルの値

つまり、LAMBDA関数はA1:C2の6セル分のループ中に6回も計算させられる。

1回目:LAMBDA(0,1,0+1^2)
2回目:LAMBDA(1,2,1+2^2)
3回目:LAMBDA(5,3,5+3^2)
4回目:LAMBDA(14,4,14+4^2)
5回目:LAMBDA(30,5,30+5^2)
6回目:LAMBDA(55,6,55+6^2)
最後:91

な感じになるので、LAMBDAのパラメータが2つあるようだ。

セル単位で処理が済むならMAP関数でループする方が簡単かも、

下図は範囲の値を2倍した範囲を作るだけ

式:=MAP(A1:C2,LAMBDA(a, a*2))

計算式はMAP関数のパラメータに範囲があるので自動的にスピルされるのでD3のみに書けばよい。

さらにMAP関数には範囲を複数指定できるらしいので、A範囲+B範囲な計算結果もつくれるっぽい。

式:=MAP(A1:C2,A4:C5,LAMBDA(x, y, x + y ))

再びLAMBDAのパラメータが2つになったけど、扱う範囲が2つなんだから仕方が無い。

同様に、BYROWやBYCOL関数もあるが、動きは(多分)予想できると思う。

SEQUENCE関数が引き渡した数だけ連番(つまり配列)を出力してくれる。

これを使うとLEN関数を組み合わせ、文字列の長さ分の連番を作ることができるから・・・

それをMAP関数に食わせると、1文字づつ何かの処理をやってくれる。

式:=LET(セル, A1, MAP(SEQUENCE(LEN(セル)), LAMBDA(x, MID(セル, x, 1))))

しかし、具体的な使い道となると、1文字コマンドなプロトコルの場合かな?

ロボットのコマンドとかで、

“1422”: “1”:右折、”4″:後退、”2″:左折

⇒ 右折、後退、後退、左折 とかかな?

更に、自己再帰処理が可能らしい。

式:下参照
=LET(対象セル,A1,
  Z,  LAMBDA(f, LET(g, LAMBDA(x, f(LAMBDA(v, LET(w, x(x), w(v) )))), g(g))),
  myfact, Z(LAMBDA(myfact, LAMBDA(x, IF(x=0, 1, x * myfact(x-1))))),
  myfact(対象セル)
)

元ネタ

元ネタの元ネタ。※リンク切れの可能性あり

再帰して欲しいと思って直行ルートで書くと

=LET(対象セル,$A$1,
  myfact,  LAMBDA(x, IF(x=0, 1, x * myfact(x-1))),
  myfact(対象セル)
)
⇒ #NAME?

そう、LET関数でのmyfact変数宣言中は「myfactの宣言が確定していない」ので、

myfact(x – 1)のmyfactなんて知らないがな?エラーが発生する。

注意1:LET文は直書きな再帰処理は不可能。

なので、事前作業として何か変数宣言して、ここを経由して名前の確定を遅延させてしまえばよい。

しかしmyfactを直接引き渡せないことに変わりは無いので、

LAMBDA関数を使って仮引数仮引数を含む処理の形で一旦処理中で参照するmyfactの実体の確定を保留しつつmyfactの宣言を先に完了させる。

=LET(対象セル,$A$1,
  Z, ・・・(検討中)・・・
  myfact, Z(LAMBDA(myfact, LAMBDA(x, IF(x=0, 1, x * myfact(x-1))))),
  IF( 対象セル=0, 0, myfact(対象セル))
) ⇒ #CALC!

LAMBDA(LAMBDA())な感じになって見苦しいなぁ・・・(泣き

さて・・・

Zは不動点コンビネータで書けばいいらしいけど、

Z = lambda f: (lambda x: f(lambda *y: x(x)(*y)))(lambda x: f(lambda *y: x(x)(*y)))

他の言語の例は無名再帰にあるらしいがEXCELは載ってない。

不動点コンビネータの1例にYコンビネータというのがあるらしく、

これをEXCELの計算式にすると

LAMBDA(f, LET(g, LAMBDA(x, f(x(x)), g(g))))

しかし式を確定しようとすると

LAMBDA関数の宣言中なのに『f(x(x))』の『x(x)』部分を直ぐに計算しようとしてバグった

つまり、今のLAMBDA関数の処理部で関数の引数の式に関数が含まれていると

即評価してしまうのではないか?(ちょっと有り得ないけどね!

そんな気がした。

『x(x)』なら、直ぐに計算しようとは思わない。(ハズ

EXCELの為に少し難易度を下げてみる。

f(x(x))をfとx(x)に分離し

x(x)の処理を一旦wとして定義してから、

x(x)を計算する様にしてみると

LAMBDA(v, LET(w, x(x), w(v) ))

となる。

この仮引数vには外側のLAMBDA関数『LAMBDA(x, f(x(x))』の仮引数xが引き渡されるハズなので、『f(x(x))』と等価になるハズだ。

これで調整しなおして

LAMBDA(f, LET(g, LAMBDA(x, f(LAMBDA(v, LET(w, x(x), w(v) )))), g(g)))

になった。

・・・

元ネタ記事では「今風」に詳しく説明されているので、間違いなくそちらの方が理解しやすいだろう。

僕はよーく読んだ末にやっぱりイミフになってしまった。※頭悪いなぁボクは!

EXCELの式の処理の都合をイメージしながら

僕なりに考察してみるしかなかった。※頭悪いなぁボクは!

だから、やはりあのゴニョゴニョなZの式

「EXCEL風Yコンビーネータ」

の様に思えた。



「EXCEL]LET関数

javascriptでも最近よく使うLET ※予約語

javascriptでは、var同様に変数宣言として使うけど・・・

EXCELでは、やはりBASICでのLET文である。

Rem LET 変数 = 計算式
LET X = SQRT(13)
=LET (変数名1, 計算式1, 変数名2, 計算式2, ・・・, セルの計算式  )

最後のセルの計算式は、この式の結果を計算するための計算式なので、

セルに何も表示しないなら、” ” でも良いかもしれない。

変数名n (n=1,2,・・・)は、このLETでのローカル変数らしくセル名リストに載らない。

なんだか使い道がさっぱり思いつかないが

=LET(範囲,A1:G1,合計,SUM(範囲), 平均, 合計/COLUMNS(範囲), "平均:" & 平均)

と書ける。改行してみると

=LET(
範囲,A1:G1,
合計,SUM(範囲),
平均, 合計/COLUMNS(範囲),
"平均:" & 平均)

つまり、LET関数は複数のセルに分けていた計算式を1つにまとめることができる。(コトもある

パっと見やすいのも高得点。

ワークシートに処理の破片みたいな計算式をバラまいてしまうとシートの端々までチェックしないと動きが読めないので大変。

そんな時はとても重宝するかもしれない。

なんたってEXCELの列は256個しかないしね。(笑



[EXCEL]ネストした表の拾い出し

こんなネストした表から(nnn)形式の番号とその後のテキストを拾い出すとしたら・・・

ネストした表

VBAではなくセルの数式で頑張ると

(nnn)を拾い出す簡易な数式
(nnn)の後のテキストを拾い出す簡易な数式

な感じだろうか・・・

一応6行目の様なケースも考慮してSEARCH関数で(nnn)な何かを判定している。

手元のEXCELは数式に改行コードが入るので、図の様に改行できて見やすい。けど、手元のPCのCPUは古いけどi9-9900でメモリ32GBだから不自由なく使えてるダケかもしれない。よくあるハズレPCアンド旧EXCELな状況ではそうもいかない。

多分、重い数式の説明やらヘルプ機能が邪魔で手に負えなくなるから、sakuraエディタで最終行を複製し1行範囲でセル座標を置換して貼り付けた方がマシな気がする。

FINDやSEARCHの検索対象を範囲指定してスピンさせる範囲分列を消費させる方法もあるけど、CONCATでかき集めないといけないし、悪質なくらいネストしている表の場合は列が足りなくなる恐れがあるので複数行な数式の方がマシな気がする。

でも行方向にスピルさせれば・・・

長すぎる番号を拾い出す数式
長すぎる番号の後のテキストを拾い出す数式

数式を行数分コピペしなくていいから便利かもしれない。

※範囲が1行分足りなくなった時に、数式の6を7に一斉置換するためにsakuraエディタが必須かも。

もし10ネストとかになると数式の長さに引っかかったりしそうだし、やっぱり編集も面倒!

「長いIF数式」をLAMBDA関数で括り、セル名の参照範囲に割り当ててみよう。

長い数式をセル名付きのLAMBDA式化
短くなった番号を拾い出す数式
短くなった番号の後のテキストを拾い出す数式

範囲パラメータを指定すると自動的にスピルされてしまうので、4つの番号取得を一つにまとめる方法が無いのかな?

複数列でまとめた数式をLAMBDA化してみよう

複数列からxxxな式を追加してみた

セル名の範囲をシートにすると「シート名!セル名」になってしまうのでセル名の範囲をブックに変更。

セルの数式の方も

もっと短くなった番号を拾い出す数式
もっと短くなった番号の後のテキストを拾い出す数式

とまれ、ここまで短くなれば十分かな。

ネストが深くなったら、

  1. 「複数列からxxx」の参照範囲のLAMBDAのパラメータと式を増やす
  2. セルの数式もパラメータ増やす

で何とかなりそう。

「複数列のxxx」セル名でBYCOL関数を使ってみる

ん-。やはり列範囲を指定した時点でスピルされてしまう。

うまく行かないや(笑

ここまで数式で頑張らなくても

シートからCSVに吐き出して、エディタでTAB(¥t)を一斉削除してシートに戻せばよいのは、秘密です。



【Microsoft365用】Excelのマクロや計算式を除外して保存する方法

【Excel】マクロや計算式を除外して保存する方法 のソースでは、

今のMicrosoft365のExcelでの動作が不安定だったので見直したものです。

Option Explicit

Public Sub アクティブなブックのセル値を値に変換して保存()
On Error GoTo err1
    Call 非表示のワークシートを削除する(ActiveWorkbook)
    Call ワークシートのセル値を値に変換して保存(ActiveWorkbook)
    Call 普通のEXCELファイルに保存(ActiveWorkbook)
exit1:
    Exit Sub
err1:
    MsgBox Err.Description
    GoTo exit1
End Sub

Private Sub 非表示のワークシートを削除する(ByRef ワークブック As Excel.Workbook)
    Dim ワークシート As Excel.Worksheet
    For Each ワークシート In ワークブック.Worksheets
      If ワークシート.Visible <> Excel.XlSheetVisibility.xlSheetVisible Then
        '確認メッセージを非表示にする設定
        Excel.Application.DisplayAlerts = False
        '非表示のワークシートを削除
        ワークシート.Delete
        '確認メッセージを表示する設定
        Excel.Application.DisplayAlerts = True
      End If
    Next
End Sub

Private Sub ワークシートのセル値を値に変換して保存(ByRef ワークブック As Excel.Workbook)
    '全シート選択
    '条件:事前に非表示なワークシートは削除済みであること
    '非表示なワークシートが残っている場合は ワークブック.Sheets(Array("Sheet1", "Sheet2")).Selectな感じで表示ワークシートに限定して選択すること )
    ワークブック.Sheets.Select
    '全ワークシートを選択
    ワークブック.Application.Cells.Select
    '全ワークシートをコピー
    ワークブック.Application.Selection.Copy
    '全ワークシートへ値としてペースト
    ワークブック.Application.Selection.PasteSpecial Paste:=xlPasteValues, _
      Operation:=xlNone, _
      SkipBlanks:=False, _
      Transpose:=False
    'コピペモードの解除
    ワークブック.Application.CutCopyMode = False
    'シート全体を選択したままになっているので、
    'A1のみ選択の状態にする
    Call ワークブック.Application.Cells(1, 1).Select
End Sub

Private Sub 普通のEXCELファイルに保存(ByRef ワークブック As Excel.Workbook)
    Dim ファイル名 As String
    '拡張子を削除 ※削除しないと拡張子が重複する
    ファイル名 = Replace(ワークブック.FullName, ".xlsm", "")
    '確認メッセージを非表示
    ワークブック.Application.DisplayAlerts = False
    '普通のExcelファイルに保存
    ワークブック.SaveAs Filename:=ファイル名, _
      FileFormat:=xlOpenXMLWorkbook, _
      Password:="", _
      WriteResPassword:="", _
      ReadOnlyRecommended:=False, _
      CreateBackup:=False
    '確認メッセージを表示
    Application.DisplayAlerts = True
    '処理完了メッセージ
    MsgBox ファイル名 & ".xlsx" & vbCrLf & "に名前を変えて保存しました"    
End Sub

事前に非表示のシートを削除することで全シート選択の仕方が簡素になってます。非表示シートをそのまま保存したい場合は前の記事を参考にしてください。

Microsoft365のExcelで動作が変だったのは「普通のEXCELを保存」です。

  1. SaveAsのFilenameには拡張子を除くフルパスなファイル名を指定するように変えました。
    • 確認メッセージを非表示にすると、
    • 「chdir パス名」で保存先フォルダを変更する方法が失敗しやすい。
  2. ReadOnlyRecommendedパラメータの名前を訂正
    • 記事にソースをペーストした際に綴りがおかしくなっていたので訂正。
    • ここは旧版でも動かないハズです。

結果的にソースが短くなったのでOKかな。(笑



[Excel VBA]WORD文書のコメントの頁番号を列挙する

今度はWORD文書のコメントの頁番号を列挙するマクロです。

EXCELのワークシートの「コメント行」セル名に「コメント」「コメント対象」「コメント頁番号」の列を追加して使用します。

コメントをワークシートに転記するマクロの内容は赤字頁番号()をコピってこんな感じに

Public Sub コメント字頁番号検索()
On Error GoTo err1
    '初期化
    Dim wordApp As Word.Application
    Set wordApp = CreateObject("Word.Application")
    Dim コメントの開始行 As Long
    コメントの開始行 = ActiveSheet.Range("タイトル行").row + 1
    Dim コメントの列 As Long
    コメントの列 = ActiveSheet.Range("タイトル行").Find("コメント").Column
    Dim コメント対象の列 As Long
    コメント対象の列 = ActiveSheet.Range("タイトル行").Find("コメント対象").Column    
    Dim 頁番号の列 As Long
    On Error Resume Next
    Err.Clear
    頁番号の列 = ActiveSheet.Range("タイトル行").Find("コメント頁番号").Column
    If Err.Number <> 0 Then
        頁番号の列 = ActiveSheet.Range("タイトル行").Find("頁番号").Column
    End If
    '検索対象のWORD文書を開く
    Dim wordFname As String
    wordFname = 検索対象のWORD文書名を調べる
    Dim wordDoc As Word.document
    Set wordDoc = 検索対象のWORD文書を開く(wordApp, wordFname)
    If TypeName(wordDoc) <> "Nothing" Then
        Dim row As Long
        row = コメントの開始行
        wordDoc.ActiveWindow.Selection.Start = 0
        wordDoc.ActiveWindow.Selection.End = 0
        Dim コメント As Word.Comment
        Dim 順番 As Integer
        順番 = 1
        Do While (コメント検索(wordDoc, 順番, コメント))
            Dim コメント対象テキスト As String
            コメント対象テキスト = コメント.Range.text
            コメント対象テキスト = Replace(コメント対象テキスト, vbCr, "")
            コメント対象テキスト = Replace(コメント対象テキスト, vbLf, "")
            コメント対象テキスト = Trim(コメント対象テキスト)
            If コメント対象テキスト <> "" Then
                'コメント
                ActiveSheet.Cells(row, コメントの列).Value = コメント.Range.text
                'コメントの対象テキスト
                コメント.Scope.Copy
                ActiveSheet.Cells(row, コメント対象の列).Select
                ActiveSheet.Paste
                Dim pasteRows As Integer
                pasteRows = Selection.Count
                Dim st, ed
                st = コメント.Scope.Start
                ed = コメント.Scope.End
                Dim 開始頁番号 As Integer
                wordDoc.ActiveWindow.Selection.Start = st
                wordDoc.ActiveWindow.Selection.End = st
                開始頁番号 = wordDoc.ActiveWindow.Selection.Information(wdActiveEndAdjustedPageNumber)
                Dim 終了頁番号 As Integer
                wordDoc.ActiveWindow.Selection.Start = ed
                wordDoc.ActiveWindow.Selection.End = ed
                終了頁番号 = wordDoc.ActiveWindow.Selection.Information(wdActiveEndAdjustedPageNumber)
                '右セルに頁番号を書き込む
                Dim 頁番号説明文 As String
                If 開始頁番号 <> 終了頁番号 Then
                    ActiveSheet.Cells(row, 頁番号の列).Value = 開始頁番号 & "~" & 終了頁番号 & "頁"
                Else
                    ActiveSheet.Cells(row, 頁番号の列).Value = 開始頁番号 & "頁"
                End If
                row = row + pasteRows
            End If
        Loop
    End If
exit1:
    Call MSWORDをそのまま閉じる(wordApp)
    Exit Sub
err1:
    MsgBox Err.Description
    GoTo exit1
End Sub

コメント検索は、順序の順にコメントを引き渡し、無くなったらFalseを返す様にしました。

パラメータのByRef指定を使って順序とコメントの内容を更新する方法は余り使わなくなった手法かもしれません。

Private Function コメント検索(ByRef wordDoc As Word.document, ByRef 順番 As Integer, ByRef コメント As Word.Comment) As Boolean
    With wordDoc.Comments
        If 順番 > .Count Then
            コメント検索 = False
            Exit Function
        End If
        Set コメント = wordDoc.Comments(順番)
        順番 = 順番 + 1
    End With
    コメント検索 = True
End Function

実際には、コメント検索やワークシートに【返信(Replies)】と【解決(Done)】の処理があった方が使い道がありそうですけどね。



[Excel VBA]WORD文書の赤色属性を含む文字の頁番号を列挙する

先の[Excel VBA]WORD文書の赤字の頁番号を列挙するでは、赤字以外の属性(抹消線など)があるテキストをヒットしなかったので、改訂版です。

今回はWORDの「高度な検索」の「書式」で「赤字」だけ設定する方法を使います。

と云っても、赤文字頁番号検索()少し変えたダケです。(笑

Public Sub 赤文字頁番号検索()
On Error GoTo err1
    '初期化
    Dim wordApp As Word.Application
    Set wordApp = CreateObject("Word.Application")
    Dim 赤字の開始行 As Long
    赤字の開始行 = ActiveSheet.Range("タイトル行").row + 1
    Dim 赤字の列 As Long
    赤字の列 = ActiveSheet.Range("タイトル行").Find("赤字").Column
    Dim 頁番号の列 As Long
    On Error Resume Next
    Err.Clear
    頁番号の列 = ActiveSheet.Range("タイトル行").Find("赤字頁番号").Column
    If Err.Number <> 0 Then
        頁番号の列 = ActiveSheet.Range("タイトル行").Find("頁番号").Column
    End If
    '検索対象のWORD文書を開く
    Dim wordFname As String
    wordFname = 検索対象のWORD文書名を調べる
    Dim wordDoc As Word.document
    Set wordDoc = 検索対象のWORD文書を開く(wordApp, wordFname)
    If TypeName(wordDoc) <> "Nothing" Then
        Dim row As Long
        row = 赤字の開始行
        wordDoc.ActiveWindow.Selection.Start = 0
        wordDoc.ActiveWindow.Selection.End = 0
        Do While (高度な赤字検索(wordDoc))
            Dim 赤字 As String
            赤字 = wordDoc.ActiveWindow.Selection.text
            赤字 = Replace(赤字, vbCr, "")
            赤字 = Replace(赤字, vbLf, "")
            赤字 = Trim(赤字)
            If 赤字 <> "" Then
                wordDoc.ActiveWindow.Selection.Copy
                ActiveSheet.Cells(row, 赤字の列).Select
                ActiveSheet.Paste
                Dim pasteRows As Integer
                pasteRows = Selection.Count
                Dim st, ed
                st = wordDoc.ActiveWindow.Selection.Start
                ed = wordDoc.ActiveWindow.Selection.End
                Dim 開始頁番号 As Integer
                wordDoc.ActiveWindow.Selection.Start = st
                wordDoc.ActiveWindow.Selection.End = st
                開始頁番号 = wordDoc.ActiveWindow.Selection.Information(wdActiveEndAdjustedPageNumber)
                Dim 終了頁番号 As Integer
                wordDoc.ActiveWindow.Selection.Start = ed
                wordDoc.ActiveWindow.Selection.End = ed
                終了頁番号 = wordDoc.ActiveWindow.Selection.Information(wdActiveEndAdjustedPageNumber)
                '右セルに頁番号を書き込む
                Dim 頁番号説明文 As String
                If 開始頁番号 <> 終了頁番号 Then
                    ActiveSheet.Cells(row, 頁番号の列).Value = 開始頁番号 & "~" & 終了頁番号 & "頁"
                Else
                    ActiveSheet.Cells(row, 頁番号の列).Value = 開始頁番号 & "頁"
                End If
                row = row + pasteRows
            End If
        Loop
    End If
exit1:
    Call MSWORDをそのまま閉じる(wordApp)
    Exit Sub
err1:
    MsgBox Err.Description
    GoTo exit1
End Sub

後は、高度な赤字検索()を追加するだけです。

中身はWORDで「高度な検索」の「書式」で「赤字」だけ設定した時のマクロ記録内容を少し調整したものです。

Private Function 高度な赤字検索(wordDoc As Word.document) As Boolean

    With wordDoc.ActiveWindow.Selection.Find
        '書式関連を初期化
        .ClearFormatting
        .Font.Color = wdColorRed
        '文字関連を初期化
        .text = ""
        .Replacement.text = ""
        .Forward = True
        .Wrap = wdFindStop '文書の終わりで中断
        .Format = True
        .MatchCase = False
        .MatchWholeWord = False
        .MatchByte = False
        .MatchAllWordForms = False
        .MatchSoundsLike = False
        .MatchWildcards = False
        .MatchFuzzy = True
        '検索
        .Execute
        '結果を取得
        高度な赤字検索 = .Found
    End With

End Function

検索対象のWORD文書名を調べる検索対象のWORD文書を開くMSWORDをそのまま閉じるは前の記事のままです。

Font.ColorとFont.ColorIndexのいづれを使うのか良いのかは悩ましいです。

後、デバッグ中は起動したMS-WORDをタスクマネージャから終了していましたが、上記のwordAppを外部変数にして、マクロ画面から終了させた方が便利そう。

Dim wordApp As Word.Application
・・・
Set wordApp = CreateObject("Word.Application")
・・・
public sub マクロ実行中に起動したMSWORDを強制終了
    Call MSWORDをそのまま閉じる(wordApp)
End Sub 


[Excel VBA]WORD文書の赤字の頁番号を列挙する「赤字以外の属性不可」

WORD文書の中の赤字を見つけるマクロを見つけた。

添削するには便利、しかしそんな便利なマクロを作ってる人は稀なので大抵は

P10、12を修正

の様に列挙したリストも渡さないといけないので、そんなマクロを作ってみた。

WORD文書には

ヘッダかフッタに頁番号のフィールドを割り当てておく。

マクロを配置するEXCELのワークシートには

な感じで対象WORD文書名、赤字、頁番号と書いたセルにタイトル行と名付け、対象WORD文書名の下に赤字を探すWORD文書のファイル名を入れておく。

赤文字頁番号検索マクロを実行すると

図の様に見つけた赤字とその頁番号がワークシートに書き込まれる。

そんなマクロのメイン処理はこんな感じ。

Public Sub 赤文字頁番号検索()
On Error GoTo err1

    '初期化
    Dim wordApp As Word.Application
    Set wordApp = CreateObject("Word.Application")
    '
    Dim 赤字の開始行 As Long
    赤字の開始行 = ActiveSheet.Range("タイトル行").row + 1

    Dim 赤字の列 As Long
    赤字の列 = ActiveSheet.Range("タイトル行").Find("赤字").Column
    
    Dim 頁番号の列 As Long
    頁番号の列 = ActiveSheet.Range("タイトル行").Find("頁番号").Column

    '検索対象のWORD文書を開く
    Dim wordFname As String
    wordFname = 検索対象のWORD文書名を調べる
    Dim wordDoc As Word.document
    Set wordDoc = 検索対象のWORD文書を開く(wordApp, wordFname)
    If TypeName(wordDoc) <> "Nothing" Then
        '赤文字を1文字選択する
        Call 赤文字を1文字選択する(wordDoc)
        '選択中の文字と同じ属性の文字を全て選択状態にする
        Call 選択中の文字と同じ属性の文字列を全部クリップボードにコピる(wordDoc)
        'ワークシートに貼り付ける
        ActiveSheet.Cells(赤字の開始行, 赤字の列).Select
        ActiveSheet.Paste
        'ワークシートの選択状態のセルの文字を検索する
        Dim row As Long
        For row = 赤字の開始行 To 赤字の開始行 - 1 + Selection.Count
            Dim text As String
            text = ActiveSheet.Cells(row, 赤字の列).Value
            Dim 頁番号 As Integer
            頁番号 = 赤字テキスト検索(wordDoc, text)
            If 頁番号 > 0 Then
                '右セルに頁番号を書き込む
                ActiveSheet.Cells(row, 頁番号の列).Value = 頁番号 & "頁"
            Else
                ActiveSheet.Cells(row, 頁番号の列).Value = "見つかりません"
            End If
            'ワークシートの選択状態の最後のセルまで繰り返す
        Next row
    End If
exit1:
    Call MSWORDをそのまま閉じる(wordApp)
    Exit Sub

err1:
    MsgBox Err.Description
    GoTo exit1

End Sub

察しが付いたと思うがとても長いし、WORD「選択」で複数個の選択状態になった場合、VBAでその結果にダイレクトにアクセスする方法がみあたらなかったので、一旦クリップボードに入れてワークシートに書き出し、1つづつ頁番号を調べているので、処理が終わるまでじっと待たなければいけない。

以下、サブルーチン群。

セル名を使っているけど、単なる趣味でしかない。

Private Function 検索対象のWORD文書名を調べる() As String
    
    Dim ファイル名列 As Integer
    ファイル名列 = ActiveSheet.Range("タイトル行").Find("対象WORD文書名").Column
    Dim wordFname As String
    wordFname = ActiveSheet.Cells(ActiveSheet.Range("タイトル行").row + 1, ファイル名列)
    'ドライブレターやパスが無い場合
    If InStr(wordFname, "\") = 0 Then
        wordFname = ActiveWorkbook.Path & "\" & wordFname
    End If
    検索対象のWORD文書名を調べる = wordFname

End Function

普通にWORD文書を開くとマクロのデバッグでSTOPしたりすると時々EXCELの裏に「壊れたWORD文書です。xxxxx」と表示されたりするので、読み取り専用で開くのが吉。

Private Function 検索対象のWORD文書を開く(wordApp As Word.Application, docPath As String) As Word.document
On Error GoTo err1

    '読み取り専用
    Set 検索対象のWORD文書を開く = wordApp.Documents.Open(Filename:=docPath, ReadOnly:=True)
    Exit Function

err1:
    MsgBox Err.Description
    Set 検索対象のWORD文書を開く = Nothing

End Function

WORDの「高度な検索」で「書式」から赤字を指定した場合のマクロ

Private Sub 赤文字を1文字選択する(wordDoc As Word.document)

    '赤文字を検索
    wordDoc.ActiveWindow.Selection.Find.ClearFormatting
    wordDoc.ActiveWindow.Selection.Find.Font.Color = wdColorRed
    
    With wordDoc.ActiveWindow.Selection.Find
        .text = ""
        .Replacement.text = ""
        .Forward = True
        .Wrap = wdFindContinue
        .Format = True
        .MatchCase = False
        .MatchWholeWord = False
        .MatchByte = False
        .MatchAllWordForms = False
        .MatchSoundsLike = False
        .MatchWildcards = False
        .MatchFuzzy = True
    End With
    
    wordDoc.ActiveWindow.Application.Selection.Find.Execute
    
    '1文字だけ選択する
    wordDoc.ActiveWindow.Selection.End = wordDoc.ActiveWindow.Selection.Start + 1

End Sub

WORDの「類似した書式の文字列を選択」のマクロ

Private Sub 選択中の文字と同じ属性の文字列を全部クリップボードにコピる(wordDoc As Word.document)

    '類似した書式の文字列を選択するWordマクロ三種     https://www.ka-net.org/blog/?p=5486
    wordDoc.Application.Run "SelectSimilarFormatting"
    'Application.CommandBars.FindControl(ID:=5946).Execute
    'Application.CommandBars.ExecuteMso "SelectTextWithSimilarFormatting"
    
    'クリップボードにコピる
    wordDoc.ActiveWindow.Selection.Copy

End Sub

赤字な特定の文字列を検索するマクロ

Private Function 赤字テキスト検索(wordDoc As Word.document, text As String) As Integer

    赤字テキスト検索 = 0
    With wordDoc.Content.Find
        '赤文字を指定
        .ClearFormatting
        .Font.Color = wdColorRed
        'テキストを指定
        .text = text
        '順方向を指定
        .Forward = True
        'いざ検索
        .Execute
        '結果は?
        If .Found = True Then
            wordDoc.ActiveWindow.Selection.Start = .Parent.Start
            wordDoc.ActiveWindow.Selection.End = .Parent.End
            '頁番号を返す
            赤字テキスト検索 = wordDoc.ActiveWindow.Selection.Information(wdActiveEndAdjustedPageNumber)
            Exit Function
        End If
    End With

End Function

意外と面倒なWORDの終了

ループしなくても

wordApp.Documents.Close SaveChanges:=wdDoNotSaveChanges

で済むらしいけど、念のためループしている。

Private Sub MSWORDをそのまま閉じる(ByRef wordApp As Word.Application)

    Dim idx As Integer
    For idx = 1 To wordApp.Documents.Count
        wordApp.Documents(idx).Close SaveChanges:=wdDoNotSaveChanges
    Next
    wordApp.Quit
    Set wordApp = Nothing

End Sub



【Excel】マクロや計算式を除外して保存する方法

資料を作る時はVLOOKUPとか、いっぱい数式が入ってる。

しかし、誰かに渡す時は安心して扱えるように全ての数式をデータに変換しておきたい時がある。

※何気にキーを押すと数式が#ERRORを吐くことがあるから

具体的には、

  1. 全シート(但し、非表示シートを除く)を選択状態
  2. 全セルを選択
  3. ワークシートをコピー
  4. 値(あるいは計算結果)を使用するモードで、ワークシートにペースト
  5. 普通のExcelファイルに保存

するダケ。

'全シート選択
Dim worksheet As Worksheet
Dim wss() As String
Dim cnt As Integer
cnt = 0
For Each worksheet In Worksheets
  If worksheet.Visible = xlSheetVisible Then
    cnt = cnt + 1
    ReDim Preserve wss(cnt -1)
    wss(cnt - 1) = woeksheet.Name
  Else
    '確認メッセージを非表示
    Application.DisplayAlerts = False
    'ワークシートを削除
    worksheet.Delete
    '確認メッセージを表示
    Application.DisplayAlerts = True
  End If
Next
Sheets(wss).Select

'コピー
Cells.Select
Selection.Copy

’データ貼り付け
Selection.PasteSpecial Paste:= xlPasteValues, _
  Operation:= xlNone, _
  SkipBlanks:= False, _
  Transpose:= False

'セル選択の解除
Application.CutCopyMode = False

全シート選択時は非表示シートが含まれているとエラるので注意。

非表示シートは不要なら削除して手渡しした方が良いかもしれないけど

確認メッセージが出るのがうざい。

そうなるとマクロも消して保存したくなる。

名前を変えて保存で、ファイルの種類を「Excel ブック (*.xlsx)」に切り替えて保存すればいいが

マクロ記録したコードでは

読めないExcelファイルを保存しまいがち

実際エラったり散々なようだ。

  • MSのサイトではエラったら・・・・・・・・
  • FileFormatパラメータは 1を使う
  • FileNameパラメータは 拡張子を省く

ことをお勧めしていた。

確かにエラーは発生しないものの。

使い物にならないEXCELファイルになってしまう

※開くと「アカン!読めんw」と泣きが入る

※開くと「一部読めんw」と呟いて、書式が壊れCSVファイルを読んだ様な画面になる

色々試した結果

こうなった。

Dim bookName as String
bookName = ActiveWorkbook.Name
bookName = Replace(ActiveWorkbook.Name,".xlsm","xlsx")
Dim bookNamePath
bookNamePath = Split(ActiveWorkbook.FullName,"\")
ReDim Preserve bookNamePath(UBound(bookNamePath)-1)
bookNamePath = Join(bookNamePath,"\")
ChDir bookNamePath
'確認メッセージを非表示
Application.DisplayAlerts = False
'普通のExcelファイルに保存
ActiveWorkbook.SaveAs Filename:= bookName, _
  FileFormat:= xlOpenXMLWorkbook, _
  Password:= "", _
  WriteResPassword:= "", _
  ReadOnlyRecommended:= False, _
  CreateBackup:= False
'確認メッセージを表示
Application.DisplayAlerts = True
'処理完了メッセージ
MsgBox bookNamePath & "\" & bookName & vbCrLf & "へ名前を変えて保存"

SaveAsのパラメータは

  • FileFormatパラメータは xlOpenXMLWorkbook を使う
  • FileNameパラメータは 拡張子をxlsx に変える

これで、確認メッセージは出てくるもののちゃんと保存できたし、マクロも消えていた。

しかし、このコードがいつのまにか使い物にならなくなっている可能性がきわめて高いのが

MSクオリティ。

PS.2022/6/10

てか、サブPCの古いExcelで動作をチェックしてたので、色々支障がありました。

今のExcelなら【Microsoft365用】Excelのマクロや計算式を除外して保存する方法を参考にしてください。

後、ReadOnlyRecommendedの綴りがまちがっていたので訂正。多分、何度も貼り換えてるうちに変になったんだろうなぁ・・・



【Excel】秒分グラフ

単位を分と秒にするとまともなグラフを作れないのはエクセルの仕様ですか?
ブチギレそうです。

~とあるTwitter から~

そうなのかな?と思ってやってみたら、見事にハマった。

失敗したグラフ

単に棒グラフを作成すると、このデータの場合は自動的に横軸の目盛りが「43秒」毎に配置されてしまう。原因は単位が60進数を考慮してない設計のせいで、適当スギる値になっているせいだ。

適当スギる単位

単位の主はセルに「0:1:0」と入力して書式を「数値」にすると、「0.0006944444」になるが、
長いので「0.000695」が良いだろう。補助線も「0.00139」ぐらいが良いだろう。※適当だけど!

では変更を加えよう!

その「1分26秒」辺りを右クリックし【軸の書式設定】を選択し、右のプロパティっぽい画面の「単位」の「主(J)」を「0.000695」に「補助(I)」を「0.000139」にする。

一通りの操作はこんな感じ。

これで、いい感じになるっぽい。

ま、適当にやっつけたダケなので、本当は「ちゃんとした簡単な方法」があるんだろうなぁ~(棒読み

※ 実は最初のグラフが余りにも醜いので、最初から書式(mm”分”ss”秒”)に変えてある。初期書式は(hh”時”mm”分”ss”秒”)だ。



【EXCEL】SUM 小計と合計

小計と合計のある表の式はSUMよりSUBTOTALが便利。
という記事を見つけた。
誤って小計を合算してしまうことを承知の上で、合計の式を = SUM(・・・)÷2 とする方法もあるが、中計(期末等)等が追加されると厄介になる。
ボクはもっと安易に 小計の計算式 =SUM(C3:C6) を =””  &  SUM(C3:C6) と文字列にすることで 小計の計算値が合計に加算されないようにして解消していた。
それはさておき
SUBTOTAL自体は、合計、平均、データの個数から標本分散を求めるまで計算方法をパラメータで指定できるので、
SUBTOTAL(9、・・・)とすると二番目のパラメータで指定する範囲の合計を計算してくれるから
ワークシート上にコンボボックスで計算方法を切り替えるようにすると便利な関数だ。
にも拘わらず、SUMと異なり、一部の計算式の結果を無視する仕様になっている。
便利といえば便利だが・・・なぜSUMと違う合計値にしたのかについては・・・

SUBTOTAL 関数

を見ると、

範囲内に他の集計値が挿入されている場合、ネストされている集計値は、計算の重複を防ぐために無視されます。

となっている。
ここの集計値とはSUBTOTALのことでSUMは含まれていない。
具体的には小計式にSUBTOTAL(・・・)の関数が含まれていれば、
=20000+SUBTOTAL(・・・) や =SUBTOTAL(・・・)+20000としても加算されないので
円単位から千円単位に変更する際に式を変更しても合計には影響しない。
でも、似たような仕様をいくつも作られても覚えるのが面倒。
さらに考察すると
SUBTOTALって何や?SUMでいいやんか!バシ!
と修正されたりするととても面倒である。
※計算設定を変えれば・・・数値っぽいから合算しますね(キリリ なモードもある。しかし、この場合は、計算方法を変えたら結果が変わるのは当たり前(キッパリ と答えられる。
なので、小計作成マクロは既に組み込まれているので、式のセルと対象のセル範囲の2つを指定して中計や合計を作成するマクロを作ってしまった方が無難である。
そして、マクロ(VBA)も読みやすいように「日本語名」で作成した方がいいだろう。




top