[VBA]一括置換処理

やりたいこと

パラメータ化した文字列に対して、一括置換を行う。

@パラメータ@は一括置換

#パラメータ#は複数置換

イメージ

出力はジェネレータを使用したダミーデータである。

置換前

 

置換後

 

 

 

ソースコード

タスクの生成モジュール

血液型A分のみ実装

レイアウト(赤字にする)のは別モジュールに分けてもいいかも?

Option Explicit
' ----------------------------------------------------------------------------------------------------------------------------------
'  定数定義
' ----------------------------------------------------------------------------------------------------------------------------------
' シート名
Private Const TEMPLATE_OUTPUT_SHEET = "置換済みテンプレート"

' 改行コード
Private Const NEW_LINE As String = vbLf

' インデント1つ(1レベル)あたりの半角スペースの数
Private Const INDENT_SIZE As Long = 4

' プレースホルダー(単一)の囲い文字
Private Const PREFIX_SINGLE_PLACEHOLDER As String = "%"
Private Const SUFFIX_SINGLE_PLACEHOLDER As String = "%"

' プレースホルダー(リスト)の囲い文字
Private Const PREFIX_LIST_PLACEHOLDER As String = "#"
Private Const SUFFIX_LIST_PLACEHOLDER As String = "#"

' プレースホルダー置換後の囲い文字
Private Const REPLACED_OPEN_MARKER As String = "<<<"
Private Const REPLACED_CLOSE_MARKER As String = ">>>"

' ----------------------------------------------------------------------------------------------------------------------------------
'  Enum定義
' ----------------------------------------------------------------------------------------------------------------------------------
Public Enum eIndentLevel
    Level0 = 0
    Level1
    Level2
    Level3
    Level4
    Level5
End Enum

' ----------------------------------------------------------------------------------------------------------------------------------
'  メソッド:Public
' ----------------------------------------------------------------------------------------------------------------------------------
Public Sub Main()
    Dim persons As Collection
    Set persons = PersonListLoader.LoadExcelDataToList
    
    Dim template As String
    template = BuildTemplate_Root(persons)

    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets(TEMPLATE_OUTPUT_SHEET)
    ws.Cells(1, 1) = template
    ws.Cells.Font.Color = RGB(0, 0, 0)
    
    Call ChangeReplacementColor
End Sub

' ----------------------------------------------------------------------------------------------------------------------------------
'  メソッド:Private
' ----------------------------------------------------------------------------------------------------------------------------------
' テンプレートを置換する
' template: 置換パラメータを含むテンプレート
' params: 置換前後のパラメータ文字列のペア
' indentLevel: 親テンプレートを基準とした相対的なインデントレベル
Private Function BuildTemplate(template As String, params As Dictionary) As String
    Dim replacedTemplate As String
    replacedTemplate = template
        
    Dim regex As regexp
    Set regex = New regexp
    
    Dim pattern As Variant
    For Each pattern In params
        Dim replacement As String
        replacement = params(pattern)
        
        ' リスト要素を置換する
        ' インデントを合わせるため、行ごと置換を行う
        regex.Multiline = True
        regex.pattern = "^[\s ]*" _
                                    & PREFIX_LIST_PLACEHOLDER _
                                    & pattern & SUFFIX_LIST_PLACEHOLDER _
                                    & "[\s ]*"
        replacedTemplate = regex.Replace(replacedTemplate, replacement)
        
        ' 単項目要素を置換する
        regex.pattern = PREFIX_SINGLE_PLACEHOLDER _
                                    & pattern _
                                    & SUFFIX_SINGLE_PLACEHOLDER
        replacedTemplate = regex.Replace(replacedTemplate, REPLACED_OPEN_MARKER & replacement & REPLACED_CLOSE_MARKER)
    Next
    
    BuildTemplate = replacedTemplate
End Function

' テンプレートに対してインデントをする
' インデントのレベルは親テンプレートに対して相対量である
Private Function ApplyIndent(template As String, indentLevel As eIndentLevel)
    ' note: regexpを利用すると末尾の改行が取れてしまうため
    ' 代替として各行に対してインデントを設定している
    Dim splitedList() As String
    splitedList = Split(template, NEW_LINE)
    Dim str As Variant
    Dim joinedString As String
    For Each str In splitedList
        joinedString = joinedString & String(indentLevel * INDENT_SIZE, " ") & str & NEW_LINE
    Next
    ApplyIndent = joinedString
End Function

' 変更箇所の色を変更する
Private Sub ChangeReplacementColor()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets(TEMPLATE_OUTPUT_SHEET)
    
    Dim targetRange As Range
    Set targetRange = ws.Cells(1, 1)
    
    Dim task As String
    task = targetRange.value
    
    ' 置換箇所の一覧を取得する
    Dim regex As regexp
    Set regex = New regexp
    regex.Global = True
    regex.Multiline = True
    regex.pattern = REPLACED_OPEN_MARKER & ".+?" & REPLACED_CLOSE_MARKER
    Dim matches As MatchCollection
    Set matches = regex.Execute(task)
    
    ' note: Characters.Deleteのメソッドがなぜか動作しないため、
    ' カラー変更 → Delete ではなく、Delete → カラー変更の順序で処理をする
    task = Replace(task, REPLACED_OPEN_MARKER, "")
    task = Replace(task, REPLACED_CLOSE_MARKER, "")
    targetRange.value = task
    
    Dim markerLength As Long
    markerLength = Len(REPLACED_OPEN_MARKER) + Len(REPLACED_CLOSE_MARKER)
    Dim i As Long
    For i = 0 To matches.count - 1
        Dim match As match
        Set match = matches.Item(i)
        Dim beginIndex As Long
        Dim charLength As Long
        beginIndex = (match.FirstIndex + 1) - (i * markerLength)
        charLength = match.Length - markerLength
        targetRange.Characters(beginIndex, charLength).Font.Color = RGB(255, 124, 128)
        targetRange.Characters(beginIndex, charLength).Font.Bold = True
    Next
End Sub

Private Function BuildTemplate_Root(persons As Collection) As String
    Dim person As PersonData
    Dim bloodAPersons As Collection
    Set bloodAPersons = New Collection
    For Each person In persons
        If (person.BloodType = "A") Then
            Call bloodAPersons.Add(person)
        End If
    Next
    
    Dim replacedTemplate As String
    replacedTemplate = BuildTemplate_BloodA(bloodAPersons)
    BuildTemplate_Root = replacedTemplate
End Function

' 血液型Aのテンプレートを展開する
Private Function BuildTemplate_BloodA(persons As Collection) As String
    
    ' 子のテンプレートを展開する
    Dim replacedTemplate_Men As String
    Dim replacedTemplate_Women As String
    Dim count As Long
    count = 0
    
    Dim person As PersonData
    For Each person In persons
        If (person.Gender = "男") Then
            If (replacedTemplate_Men <> "") Then
                replacedTemplate_Men = replacedTemplate_Men & NEW_LINE
            End If
            replacedTemplate_Men = replacedTemplate_Men & BuildTemplate_BloodA_Men(person)
        ElseIf (person.Gender = "女") Then
            If (replacedTemplate_Women <> "") Then
                replacedTemplate_Women = replacedTemplate_Women & NEW_LINE
            End If
            replacedTemplate_Women = replacedTemplate_Women & BuildTemplate_BloodA_Women(person)
        End If
        count = count + 1
    Next
    
    ' 子テンプレートにインデントを加える
    replacedTemplate_Men = ApplyIndent(replacedTemplate_Men, eIndentLevel.Level1)
    replacedTemplate_Women = ApplyIndent(replacedTemplate_Women, eIndentLevel.Level1)
    
    ' テンプレートを取得する
    Dim template As String
    template = SZZ_TemplateSheetAccessor.GetTempate(eTemplateRowPoisition.BloodA)
    
    ' プレースホルダーを設定する
    Dim params As Dictionary
    Set params = New Dictionary
    Debug.Print (replacedTemplate_Men)
    Call params.Add("血液型A男性リスト", replacedTemplate_Men)
    Call params.Add("血液型A女性リスト", replacedTemplate_Women)
    Call params.Add("血液型Aの人数", count)
    
    ' テンプレートを展開する
    Dim replacedTemplate As String
    replacedTemplate = BuildTemplate(template, params)
    BuildTemplate_BloodA = replacedTemplate
    Debug.Print
End Function

' 血液型A_男性のテンプレートを展開する
Private Function BuildTemplate_BloodA_Men(person As PersonData) As String
    
    ' テンプレートを取得する
    Dim template As String
    template = SZZ_TemplateSheetAccessor.GetTempate(eTemplateRowPoisition.BloodA_Man)
    
    ' プレースホルダーを設定する
    Dim params As Dictionary
    Set params = New Dictionary
    Call params.Add("名前", person.Name)
    Call params.Add("ひらがな", person.NameKana)
    Call params.Add("年齢", person.Age)
    Call params.Add("職業", person.BirthDate)
    Call params.Add("電話番号", person.Phone)
    
    ' テンプレートを展開する
    Dim replacedTemplate As String
    replacedTemplate = BuildTemplate(template, params)
    BuildTemplate_BloodA_Men = replacedTemplate
End Function

' 血液型A_女性のテンプレートを展開する
Private Function BuildTemplate_BloodA_Women(person As PersonData) As String
    
    ' テンプレートを取得する
    Dim template As String
    template = SZZ_TemplateSheetAccessor.GetTempate(eTemplateRowPoisition.BloodA_Woman)
    
    ' プレースホルダーを設定する
    Dim params As Dictionary
    Set params = New Dictionary
    Call params.Add("名前", person.Name)
    Call params.Add("ひらがな", person.NameKana)
    Call params.Add("年齢", person.Age)
    Call params.Add("マイナンバーカード", person.MyNumber)
    Call params.Add("メールアドレス", person.Email)
    
    ' テンプレートを展開する
    Dim replacedTemplate As String
    replacedTemplate = BuildTemplate(template, params)
    BuildTemplate_BloodA_Women = replacedTemplate
End Function

 

テンプレート取得モジュール

Option Explicit

Private Const TARGET_SHEET_NAME  As String = "テンプレート"
Private Const TEMPLATE_COLUMN_POSITION = 2

Public Enum eTemplateRowPoisition
    All = 1
    BloodA
    BloodB
    BloodO
    BloodAB
    BloodA_Man
    BloodA_Woman
    BloodB_Man
    BloodB_Woman
    BloodO_Man
    BloodO_Woman
    BloodAB_Man
    BloodAB_Woman
End Enum

' テンプレートを取得する
Public Function GetTempate(templateRowPosition As eTemplateRowPoisition) As String
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets(TARGET_SHEET_NAME)
    GetTempate = ws.Cells(templateRowPosition, TEMPLATE_COLUMN_POSITION)
End Function