0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

COBOLのCOPY句をExcelの表にする③

Posted at

やっと桁位置計算ロジック

処理の流れからVBAのコードを作成

VBAのコードは以下のとおり(日本語よりこちらの方が見やすいかも)

Module1.bas(ReculcDigitPos)
'*********************************
'** 桁位置再計算処理
'*********************************
'** 返却値
'**   0:正常
'**   1:REDEFINES元項目に桁位置が編集されていない
'**   2:REDEFINES定義元項目が存在しない
'*********************************
Public Function ReculcDigitPos() As Integer
        
    Dim X1 As Long  '使い捨て添え字
    Dim Lng_LastRow As Long
    
    '制御配列の初期化
    Erase CA
    
    ReculcDigitPos = 0
    
    'レコード長算出のため。最終行にFILLERを追加
    Lng_LastRow = EditSheet.Cells(1, 1).CurrentRegion.Rows.Count + 1
    '最終行に01レベルでFILLER X(01)を追加
    EditSheet.Range("A" & Lng_LastRow) = "01"
    EditSheet.Range("B" & Lng_LastRow) = "レコード長"
    EditSheet.Range("C" & Lng_LastRow) = "X(01)"
    
    '管理変数の初期化
    Idx1 = -1
    Lng_NowRow = 2
    Lng_NextDigitPos = 1
    
    Do Until Trim(EditSheet.Range("A" & Lng_NowRow).Value) = ""
        
        '*** REDEFINES項目(E列)に値が編集されている場合制御配列に登録する。
        If Trim(EditSheet.Range("E" & Lng_NowRow).Value) <> "" Then
            Idx1 = Idx1 + 1
            CA(Idx1).Str_Category = "R"
            CA(Idx1).Lng_RedefinesPos = Lng_NextDigitPos
            CA(Idx1).Int_Level = CInt(Trim(EditSheet.Range("A" & Lng_NowRow).Value))
            CA(Idx1).Lng_Row = Lng_NowRow
            
            '現在位置から上方にREDEFINES元項目名を探す。
            X1 = Lng_NowRow - 1
            Do Until X1 <= 1
                If Trim(EditSheet.Range("B" & X1).Value) = Trim(EditSheet.Range("E" & Lng_NowRow).Value) Then
                    If Trim(EditSheet.Range("F" & X1).Value) <> "" Then
                        '再定義元の桁位置を編集
                        Lng_NextDigitPos = CLng(Trim(EditSheet.Range("F" & X1).Value))
                        Exit Do
                    Else
                        '再定義元の桁位置が編集されていない(基本ありえない)
                        ReculcDigitPos = 1
                        Exit Function
                    End If
                End If
                X1 = X1 - 1
            Loop
            
            'REDEFINES元項目名が存在しない
            If X1 <= 1 Then
                ReculcDigitPos = 2
                Exit Function
            End If
        End If
        
        '*** OCCURS項目(D列)に値が編集されている場合制御配列に登録する。
        '*** ただし、OCCURS繰り返し中は登録しない。
        If Trim(EditSheet.Range("D" & Lng_NowRow).Value) <> "" Then
            '制御配列に何も登録されてない場合、登録する
            If Idx1 = -1 Then
                Idx1 = Idx1 + 1
                Call AddControlArrayForOccurs
            Else
            '制御配列に何か登録されている場合
                If CA(Idx1).Lng_Row = Lng_NowRow Then
                Else
                    Idx1 = Idx1 + 1
                    Call AddControlArrayForOccurs
                End If
            End If
        End If
        
        '*** 開始桁位置を編集する。ただし、既に編集済みの場合は編集しない。
        If Trim(EditSheet.Range("F" & Lng_NowRow).Value) = "" Then
            EditSheet.Range("F" & Lng_NowRow).Value = Lng_NextDigitPos
        End If
        
        '*** 次行桁位置を算出する
        Lng_NextDigitPos = Lng_NextDigitPos + GetDigitNum(Trim(EditSheet.Range("C" & Lng_NowRow).Value))
        
        '*** 現在行をカウントアップする
        Lng_NowRow = Lng_NowRow + 1
        
        '*** 制御配列の終了判定を行う
        Do Until False
                        
            If Idx1 < 0 Then
                Exit Do
            End If
            
            '現在のLEVEL≦制御配列(Idx1)のLEVELの場合
            If CInt(Trim(EditSheet.Range("A" & Lng_NowRow).Value)) <= CA(Idx1).Int_Level Then
                
                '区分が"R"の場合
                If CA(Idx1).Str_Category = "R" Then
                    '次行桁位置
                    Lng_NextDigitPos = CA(Idx1).Lng_RedefinesPos
                    '制御配列のクリア
                    Call ClearControlArray(Idx1)
                    Idx1 = Idx1 - 1
                Else
                '区分が"O"の場合
                    'OCCURS回数(Idx1)>現在の繰返数(Idx1)
                    If CA(Idx1).Int_OccursNum > CA(Idx1).Int_NowOccursNum Then
                        CA(Idx1).Int_NowOccursNum = CA(Idx1).Int_NowOccursNum + 1
                        Lng_NowRow = CA(Idx1).Lng_Row
                        Exit Do
                    Else
                    'OCCURS回数(Idx1)≦現在の繰返数(Idx1)
                        '制御配列のクリア
                        Call ClearControlArray(Idx1)
                        Idx1 = Idx1 - 1
                    End If
                End If
            Else
                Exit Do
            End If
        Loop
    Loop
    
    '追加した最終行を削除
    EditSheet.Rows(Lng_LastRow & ":" & Lng_LastRow).Delete Shift:=xlUp
    EditSheet.Range("A1").Select

End Function
Module1.bas(AddControlArrayForOccurs)
'*********************************
'** OCCURSのための制御配列追加
'*********************************
Public Sub AddControlArrayForOccurs()
    CA(Idx1).Str_Category = "O"
    CA(Idx1).Int_Level = CInt(Trim(EditSheet.Range("A" & Lng_NowRow).Value))
    CA(Idx1).Int_OccursNum = CInt(Trim(EditSheet.Range("D" & Lng_NowRow).Value))
    CA(Idx1).Int_NowOccursNum = 1
    CA(Idx1).Lng_Row = Lng_NowRow
End Sub
Module1.bas(GetDigitNum)
'*********************************
'** 属性・桁文字列から項目桁数を返却
'*********************************
Public Function GetDigitNum(Str_PicString As String) As Integer
    
    Dim Int_KPos As Integer       '"("の出現位置
    Dim Str_DCount As String  '桁数文字列
    Dim X1 As Long
    
    'PICTURE文字列から桁数(バイト数)を取得する
    'とりあえず、9(xx),X(xx),N(XX),S9(xx),S9(xx) COMP-3を対応する
    '9(xx)V9(xx)や9(xx)V99等、小数点項目は必要であれば追加する
    GetDigitNum = 0
    Int_KPos = InStr(Str_PicString, "(")
    If Int_KPos > 0 Then
        X1 = Int_KPos + 1
        Str_DCount = ""
        Do Until Mid(Str_PicString, X1, 1) = ")"
            Str_DCount = Str_DCount & Mid(Str_PicString, X1, 1)
            X1 = X1 + 1
        Loop
        
        'N(99)は2バイト文字
        If InStr(Str_PicString, "N(") > 0 Then
            GetDigitNum = CInt(Str_DCount) * 2
            Exit Function
        End If
        
        'COMP-3指定がある場合、+1して2で割るのが計算式。端数の丸めが気になるので以下の計算とする。
        '偶数の場合+2して2で割る。奇数の場合+1して2で割る
        If InStr(Str_PicString, "COMP-3") > 0 Then
            If (CInt(Str_DCount) Mod 2) > 0 Then
                GetDigitNum = (CInt(Str_DCount) + 1) / 2
            Else
                GetDigitNum = (CInt(Str_DCount) + 2) / 2
            End If
            Exit Function
        End If
        
        'その他の場合はそのまま
        GetDigitNum = CInt(Str_DCount)
        Exit Function
    Else
        '"()"が無いPICTURE文字列「99V99」等は今回考慮しない。必要であれば追加する。
        '・・・
    End If

End Function

少々コードの解説

  • 開始時に各種管理変数に以下のとおり初期値を編集する。
    管理変数の初期化.png
  • 制御配列添字に「-1」を設定しているのは、制御配列を固定配列としているため。Uboundが使用できないため添え字に「-1」を設定して制御配列に登録済かどうか判断に使用する。
  • 制御配列を動的配列にしない理由は、配列の削除とリサイズが面倒くさそうだったのと、汎用機COBOLでは動的配列が使用できないため、慣れている固定配列を使用することとした。
  • 制御配列と管理変数をグローバル変数として定義しているため、subやfunctionで処理を分けたとしても、気にしないでそのまま変数を使用できる。とてもCOBOL的な考え方だ。単品ツールとしての使用を想定しているので、他モジュールとの連携とか気にしないで変数定義を行える。(もちろん、エクスポートして標準モジュールとして他のExcelツールで使用する場合は、しっかりと変数定義を考え直してコード化した方が良い)
  • GetDigitNum(属性・桁文字列から桁数を算出)は、今回使用する分のみコード化している。本来は小数点項目「9(02)V9(02)」等、マニュアルを参照すれば他にも沢山あるので、必要であれば追加する。
  • レコード長算出用に、最後の行の次の行に01レベル項目を追加する。

机上デバッグ開始

いよいよ机上デバッグを開始する。[COBOLのCOPY句をExcelの表にする②]で掲載した日本語の処理の流れか、上記の「ReculcDigitPos」を見ながら追いかけていく。今回私は先に日本語の処理の流れを作成して机上デバッグを実施したので、その流れに沿って説明していこう。

LEVELと項目名だけの行(集団項目)

まず最初は集団項目の行について。今回サンプルで用意したコピー句レイアウト表だと「XXサンプルレコード」、「XX集団項目01」、「XX項目02」等が該当する。
この行の場合、やることは少ない。Excel表の桁位置(F列)に管理変数の「次行桁位置」を編集するだけだ。集団項目は属性・桁(C列)が設定されていないため、「次行桁位置」の計算は不要だ。以下の状態がExcel表4行目まで到達した制御配列、管理変数の状態だ。
もちろんREDEFINESもOCCURSも出現していないので制御配列には何も管理していない。
机上デバッグ01_1.png
机上デバッグ01_2.png

LEVELと項目名と属性・桁が編集されている行

属性・桁(C列)が設定されている場合、管理変数の「次行桁位置」に、その項目の桁数を加算する。例えば9(02)は2バイトなので「次行桁位置 = 次行桁位置 + 2」を計算する。
Excelの7行目まで計算した状態は以下のとおり。まだ制御配列には何も登録されていない。
机上デバッグ02_1.png
机上デバッグ02_2.png

REDEFINES(E列)が編集されている場合

REDEFINES(E列)に項目名が編集されている場合「再定義」を意味することは前述した。
机上デバッグ03_1.png
例えば、Excelの8行目はREDEFINES項目に「XX項目02」が設定されている。意味としては『集団項目「XX項目02」の領域を、別の項目「XX項目02R」として使用する』ということになる。再定義なので、桁位置は「XX項目02」と同じ「3」から始まる。

REDEFINES(E列)が出現したので管理配列に登録する

REDEFINES項目が出現したので制御配列に登録する。制御配列に登録した直後の状態は以下のとおり。REDEFINES項目が出現した場合、「OCCURS回数」と「現在の繰返数」は使用しない(つまり初期値のまま)。
「再定義元次の桁位置」に管理変数の「次行桁位置(13)」を編集している。これが後に重要な値(再定義から脱した際に次の項目の開始桁位置)となる。
管理変数の「次行桁位置」には、「XX項目02」でB列を上方に検索し、その桁位置「3」を編集する。これが「XX項目02R」の桁位置となる。
机上デバッグ03_1.png
机上デバッグ03_2.png
この後、「現在行の設定値で管理配列の終了判定を行う」。

REDEFINESの配下から抜ける場合

Excelの9行目はLEVEL(A列)が「5」であり、制御配列[0]のLEVELが「5」である。この場合、制御配列[0]に登録したREDEFINESの配下から抜ける。
管理変数の「次行桁位置」に制御配列[0]の「再定義元次の桁位置」の「13」を編集する。
終了判定を行って、管理配列[0]をクリアした状態が以下のとおりとなる。
机上デバッグ05_1.png
机上デバッグ05_2.png

再度REDEFINES(E列)が出現したので制御配列に登録する

Excelの9行目は再度REDEFINES項目(E列)が設定されれいるので、制御配列に登録する。方法は8行目のREDEFINES項目が出現した時と同じで、制御配列[0]に登録し、管理変数の次行桁位置には「XX項目02」でB列を上方に検索し、桁位置「3」を編集する。制御配列と管理変数の編集値は以下のようになる。
机上デバッグ06_1.png
Excelの9行目には属性・桁(C列)が設定されていないため、管理変数の次行桁位置は「3」のまま、管理変数の「現在Excel行位置」はカウントアップされ「10」となる。

次の行が制御配列に登録したREDEFINES項目の配下項目の場合

Excelの10行目のLEVELは「7」であり、制御配列[0]のLEVELは「5」である。この場合、先に登録した制御配列[0]のREDEFINES項目の配下とみなし、制御配列[0]はそのままとする。
Excelの14行目まで進んだ状態は以下のとおり。
机上デバッグ07_1.png
机上デバッグ07_2.png

再びREDEFINESの配下から抜ける

Excelの14行目はLEVEL(A列)が「5」であり、制御配列[0]のLEVELが「5」であるため、制御配列[0]に登録したREDEFINESの配下から抜ける。
管理変数の「次行桁位置」に制御配列[0]の「再定義元次の桁位置」の「13」を編集する。
終了判定を実施し、管理配列[0]をクリアした状態が以下のとおり。
机上デバッグ08_1.png
机上デバッグ08_2.png

三度REDEFINES(E列)が出現したので制御配列に登録する

後は同じことの繰り返しである。机上デバッグではテーブルの値や各種変数の更新を正確に実施しないと(正しいにしろ、間違っているにしろ)正しい結果が得られない。
Excelの17行目まで進んだ状態は以下のとおり。
机上デバッグ09_1.png
机上デバッグ09_2.png

三度REDEFINESの配下から抜ける

Excelの17行目はLEVEL(A列)が「3」であり、制御配列[0]のLEVELが「5」であるため、制御配列[0]のREDEFINESの配下から抜ける。
この時に注意しなければならないのが、管理変数の「次行桁位置」の値だ。REDEFINES元項目の「XX項目02」は10バイトの集団項目だった。そのため、次の項目の桁位置は「13」である。ところが、「XX項目02-2」の集団項目の桁数は9バイトしかない。したがって管理変数の「次行桁位置」は「12」となっている。このままだとExcelの17行目「FILLER」の桁位置が「12」となってしまうのだが、ここで生きてくるのが制御配列[0]の「再定義元次の桁位置」の値「13」である。
この「13」は、最初にREDEFINES(E列)が出現した時の、管理変数の「次行桁位置」に編集されていた「13」だ。これをずっと持ち廻っていた。

COBOLのREDEFINES定義の仕様

なぜこんなことができるかというと、COBOLのREDEFINES定義の仕様のためだ。
REDEFINES定義は、「REDEFINES元項目とREDEFINES項目の間に関係ない項目は定義できない」という仕様がある。要するに、REDEFINES元項目「XX項目02」とREDEFINES項目「XX項目02R」の間には、例えば「XX項目99 X(01)」等、関係のない項目があってはならない。REDEFINESの定義は連続していなければならない。

000000     05  XX項目02.
000000       07  XX項目02A                  PIC  X(05).
000000       07  XX項目02B                  PIC  X(05).
↓↓↓ この定義はコンパイルエラーになる ↓↓↓
000000     05  XX項目99                      PIC  X(01).
↑↑↑ この定義はコンパイルエラーになる ↑↑↑
000000     05  XX項目02-1                  REDEFINES XX項目02.
000000       07  XX項目02-1-1            PIC  X(02).
000000       07  XX項目02-1-2            PIC  9(03).
000000       07  XX項目02-1-3            PIC  X(04).
000000       07  XX項目02-1-4            PIC  9(01).
000000     05  XX項目02-2                  REDEFINES XX項目02.
000000       07  XX項目02-2-1            PIC  9(07).
000000       07  XX項目02-2-2            PIC  9(02).

また、「REDEFINES項目はREDEFINES元項目のバイト数を超えてはならない」という掟もある。
REDEFINES元項目「XX項目02」が10バイトで定義されているのに、REDEFINES項目「XX項目02-1」が11バイトあってはならない。コンパイルエラーとなる。

000000     05  XX項目02.
000000       07  XX項目02A                  PIC  X(05).
000000       07  XX項目02B                  PIC  X(05).
000000     05  XX項目02-1                  REDEFINES XX項目02.
000000       07  XX項目02-1-1            PIC  X(02).
000000       07  XX項目02-1-2            PIC  9(03).
000000       07  XX項目02-1-3            PIC  X(04).
000000       07  XX項目02-1-4            PIC  9(01).
↓↓↓ この定義はコンパイルエラーになる ↓↓↓
000000       07  XX項目02-1-5            PIC  9(01).
↑↑↑ この定義はコンパイルエラーになる ↑↑↑
000000     05  XX項目02-2                  REDEFINES XX項目02.
000000       07  XX項目02-2-1            PIC  9(07).
000000       07  XX項目02-2-2            PIC  9(02).

ただし、「REDEFEINES項目はREDEFINES元項目のバイト数範囲内であればよい」ので、「XX項目02-2」は9バイト定義でもコンパイルエラーにはならない。
そして、これは知らなかったのだが以下のようなコードも許されるとのこと。REDEFINESする領域が連続していればREDEFINES元項目名を変更してもコンパイルエラーにはならない。
(COPY句定義としてはあまり意味がないような気がするのだが、コンパイルエラーにはならないとのこと)

000000     05  XX項目02.
000000       07  XX項目02A                  PIC  X(05).
000000       07  XX項目02B                  PIC  X(05).
000000     05  XX項目02-1                  REDEFINES XX項目02.
000000       07  XX項目02-1-1            PIC  X(02).
000000       07  XX項目02-1-2            PIC  9(03).
000000       07  XX項目02-1-3            PIC  X(04).
000000       07  XX項目02-1-4            PIC  9(01).
↓↓↓ REDEFINES元項目を変えてもコンパイルエラーにはならない ↓↓↓
000000     05  XX項目02-2                  REDEFINES XX項目02ー1.
↑↑↑ REDEFINES元項目を変えてもコンパイルエラーにはならない ↑↑↑
000000       07  XX項目02-2-1            PIC  9(07).
000000       07  XX項目02-2-2            PIC  9(02).

REDEFINES句が入れ子で定義されていた場合

REDEFINES句が入れ子で定義されている場合はどうなるだろうか?例えば以下のようなコピー句も作ろうとすれば作れる。Geminiに問い合わせてみたら特に問題はないとのこと。

コピー句を手作成してここまで説明してきた。REDEFINES句の入れ子定義が問題ないのか自信がなかったので、ここで初めてGeminiを利用して手作成コピー句がコンパイルを通過するのか問い合わせてみた。結果、REDEFINES句の入れ子定義については問題なく可能だということが分かった。しかし、以下のREDEFINES句とOCCURS句の重ね掛けがNGだと指摘されてしまった。
  誤:03 XX項目05R PIC 9(03) OCCURS 3 TIMES REDEFINES XX項目05.
  正:03 XX項目05R REDEFINES XX項目05 PIC 9(03) OCCURS 3 TIMES.

「REDEFINES 句はデータ名の直後に書かなければなりません。PIC 句や OCCURS 句の後ろに書くことはできません。」 とのことだった。今回の記事では桁位置計算がメインなので、COPY句テキストファイルはあまり出てこない。こっそり修正しておくことにする。

000000     05  XX項目02.
000000       07  XX項目02A                  PIC  X(05).
000000       07  XX項目02B                  PIC  X(05).
000000     05  XX項目02-1                  REDEFINES XX項目02.
000000       07  XX項目02-1-1            PIC  X(02).
000000       07  XX項目02-1-2            PIC  9(03).
↓↓↓ REDEFINES定義の入れ子状態。問題なくコンパイルは通る。 ↓↓↓ 
000000       07  XX項目02-1-2R          REDEFINES XX項目02-1-2.
000000         09  XX項目02-1-2R1      PIC  9(01).
000000         09  XX項目02-1-2R2      PIC  9(01).
000000         09  XX項目02-1-2R3      PIC  9(01).
↑↑↑ REDEFINES定義の入れ子状態。問題なくコンパイルは通る。 ↑↑↑ 
000000       07  XX項目02-1-3            PIC  X(04).
000000       07  XX項目02-1-4            PIC  9(01).
000000     05  XX項目02-2                  REDEFINES XX項目02.
000000       07  XX項目02-2-1            PIC  9(07).
000000       07  XX項目02-2-2            PIC  9(02).
000000   03  FILLER                              PIC  X(07).

上記のようなコピー句定義は、これまで長いことCOBOL言語に携わっているが一度も見たことはないし、設計としてはいかがなものかと思うのだが、文法的には正しい。これも正しく桁位置計算できるのだろうか?

REDEFINES定義が入れ子になっているだけで、制御配列の登録や管理変数の動きは同じであるため、正しく桁位置計算は可能だ。試した結果は以下となる。
机上デバッグ13_1.png

持ち廻っていた再定義元次の桁位置を次行桁位置とする

前述したとおり、制御配列[0]から抜ける際に制御配列[0]の再定義元次の桁位置「13」を、管理配列の次行桁位置に編集する。終了判定後、制御配列[0]をクリアした状態が以下のようになる。
机上デバッグ10_1.png
机上デバッグ10_2.png
Excelの17行目の桁位置を編集する。やっとここで持ち廻っていた次行桁位置が生きてくる。
机上デバッグ11_1.png

ここで気が付いた

ここまで来てやっと気が付いたのだが、制御配列の「Excel行位置」は編集しているが全く使っていない。REDEFINESの編集では不要だ。(遡って修正するのは面倒なのでやらないことにする。後述するOCCURS管理では使用する)

OCCURS(D列)が編集されている場合

次にOCCURS数(D列)が編集されている場合をトレースしてみる。
Excelの23行目まで進んだとすると、Excel表と制御配列、管理変数は以下のようになっている。
机上デバッグ12_1.png
机上デバッグ12_2.png
制御配列には何も登録されていない。Excelの23行目のOCCURS数(D列)には「2」が編集されている。集団項目「XX項目04A」を2回繰り返すという意味だ。OCCURS数が出現したので制御配列[0]に登録する。この時、制御配列の「現在の繰返数」には「1」を編集する。
机上デバッグ14_1.png

OCCURSの入れ子が発生した場合

OCCURS句の入れ子が発生した場合、考え方は同じである。Excelの24行目はOCCURS(D列)に「3」が設定されているため、続けて制御配列に登録する。制御配列[1]まで登録した結果は以下のとおり。
机上デバッグ15_1.png
Excelの26行目まで来た時点の桁位置編集と制御配列、管理変数は以下のようになっている。
机上デバッグ16_1.png
机上デバッグ16_2.png

OCCURS定義の配下

Excelの26~28行目は、通常の項目の桁位置計算と同じだ。[ここ]でも記載したが「COMP-3」はパック十進数を示す。データの格納形式が圧縮される。さらに「S9」項目なのでサイン(符号)が付加される。いわゆる「符号付パック十進数項目」ということになる。SJISやUFT-8のシステムとデータのやり取りをする場合、そのまま転送すると文字化けするので問題となる部分である。
とは言え、桁位置計算は通常の項目と同様加算しているだけだ。
Excelの29行目までの状態は以下のとおりである。
机上デバッグ17_1.png
机上デバッグ17_2.png

OCCURS定義から外れる

Excelの29行目はLEVELが「3」であるため、制御配列に登録してある全てのLEVELから外れるのだが、まだExcelの25行目のOCCURS[1]が終わったばかりである。「XX項目04C」は4回繰り返しが必要であるため制御配列[2]の「現在の繰返数」をカウントアップし、制御配列[2]の「Excel行位置」を管理変数の「現在Excel行位置」に編集する。制御配列と管理変数は以下のようになる。
机上デバッグ18_1.png
そしてExcelの29行目が出現するまで、制御配列[2]の「現在の繰返数」が「4」になるまで繰り返す。

繰り返し2回目以降の桁位置は、編集する箇所がないため桁位置の計算のみ実施していることになる。であれば、上記の様なCOPY句の定義であれば「XX項目04C」の全体的なバイト数を算出し、「バイト数×4×3×2」で「XX項目05」の桁位置が出せるのではないか?と考えるかもしれないが、OCCURS句の定義パターンは上記のようなパターンばかりではないので単純計算はできない。バラバラに考えることで、どのようなパターンのOCCURS句の入れ子が発生しても、また、OCCURS定義の中にREDEFINES句が定義されたとしても桁位置計算は対応可能である。

OCCURSの指定の繰り返しが終わる

制御配列[2]の「OCCURS回数 ≦ 現在の繰返数」となった場合、制御配列[2]の終了判定で制御配列[2]がクリアされる。その場合の制御配列と管理変数は以下のようになっている。また、Excel表は見えない部分で桁位置を計算しているのでそのイメージも載せる。
机上デバッグ19_1.png
机上デバッグ19_2.png

次の階層のOCCURS定義の繰り返し

制御配列[2]の終了判定では、制御配列[2]がクリアされるだけではなく、LEVELとしては制御配列[1]も終了であるが、「OCCURS回数[1] > 現在の繰返数[1]」であるため、現在の繰返数をカウントアップし、制御配列[1]の2回目の繰り返しが始まる。そしてExcelの25行目で新たにOCCURS 4の「XX項目04C」が出現するため、制御配列[2]に登録する。制御配列と管理変数の状態は以下のようになる。
机上デバッグ20_1.png

後は同じことの繰り返し

後は同じことを繰り返すだけだ。制御配列[n]の「現在の繰返数」をカウントアップしつつ、繰り返しが終了したら制御配列[n]をクリアし、現在のExcel行位置を戻す。すべての繰り返しが終わるまで繰り返す。

OCCURSが入れ子になってない場合

OCCURS句が入れ子になっていない場合も考え方は同じである。制御配列[0]に登録し、ロジックに従い繰り返す。Excelの29行目の「XX項目05」も制御葉配列の登録、管理変数の編集、繰り返し処理はこれまでと同様に行う。

レコード長の計算

処理の最初にExcel表の最後にレコード長計算用の架空の01レベル項目を追加している。今回のCOPOY句の例では最後のFILLERで制御配列もきれいにクリアされ、管理変数の次行桁位置も問題なく計算されるが、OCCURS句定義でCOPY句が終わっているような場合、レコード長が正しく算出されない。例えば以下のようなCOPY句定義の場合である。
机上デバッグ21_1.png
大概の場合、項目追加を予想しFILLERを多めに設定するので、あまり上記のような設定は見かけないのだが、コンパイルは問題なくとおる。したがって、架空の「01」LEVELの項目を設定することで、正しいレコード長を取得する。
机上デバッグ22_1.png

COPY句レイアウト表の桁位置計算が終了する

レコード長を計算しB1セルに編集した結果のCOPY句レイアウト表は以下のようになる。
机上デバッグ23_1.png

その昔、自分で作ったロジックは今も使える

約25年くらい前に自分で作ったロジックは、そのままそっくりコピーしたわけではなく、別の考え方を導入して構造体を使ったりしながら(当時は個別項目の固定配列で作っていた)新たにロジックを作ってみた。COPY句テキストファイルからExcelのコピー句レイアウト表を作成する一番手の掛かったロジックはREDEFINES句、OCCURS句を含めた桁位置計算だった。ロジック自体はそんなに大きくはないし、どちらかというとテキストファイルからExcelに起こす部分の方がロジックは長い。しかし、一番頭を使うのは桁位置計算だ。

もしかしたら、現在であればAIに適切な指示が出せれば、あっという間にロジックを作り出してくれるかもしれない。短い時間でサクッと作りたいのであればAIに任せれば上手くできるかもしれない。だが、こういう面倒な部分を自分の頭で考え、ロジックに落としこむのは何と楽しいことだろうか。人間はそれを手放してはいけないような気がする。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?