将Excel数据导出到固定宽度的文本文件 - 字段位置

时间:2017-12-08 15:43:29

标签: excel vba excel-vba delimited-text

首先让我说我对使用分隔文件有点新意。我试图模拟一个软件如何使用Excel布局文本文件。

以下是我用来从工作表创建文本文件的代码:

{{1}}

我试图模拟的软件有“数据位置”和“字段大小”。例如,一个字段的数据位置为77,这意味着它将作为文本文件中该行的第77个字符开始。 (我不知道这有多常见,所以如果它很常见,请原谅无用的信息。)字段大小为12。

如果这没有意义,这里是文本文件的屏幕截图。第一行显示了我的VBA创建的内容,第二行显示了我希望它的外观。如何强制工作表上的值根据它所在的列在该行的某个位置开始?

enter image description here

1 个答案:

答案 0 :(得分:0)

看起来您选择的第一行包含字段的宽度GetFieldControl。在您的问题描述中,您提到了数据位置和字段大小。您需要将此信息放在某处。您可以将它放在文件中的另一个工作表上,这可以让您调整文本文件的输出,或者您可以将这些值作为常量放在VBA代码中,或者您可以创建一个类。使用这样的东西可以让你根据需要重新定义字段。下面的示例使用模块中的简单类和一些私有函数

在下面的示例中,您需要添加名为“FieldControl”的工作表并将相应的值放在列中。请参阅GetFieldControl函数。为了测试代码我使用了以下内容:

enter image description here

您需要将以下 参考 添加到宏工作簿中。在“工具”菜单下的“VBA编辑器”中,选择“引用”,然后在出现对话框时选择“Microsoft脚本运行时”。 (工具>参考文献)

在所有与代码相关的事情上,都可以对此进行改进。

祝你好运

Class (Insert-> Class)将默认名称更改为clField(您可以随意调用它,但请确保更新dim语句Option Explicit Public Enum eFieldType Number Text End Enum Public Name As String Public Size As Long Public StartPos As Long Public Value As String Public FieldType As eFieldType 函数以匹配你给它的名字。)

Option Explicit
Option Base 1    'This makes any defined array start a 1 rather than 0

Sub Export_Selection_As_Fixed_Length_File()
     ' Dimension all  variables.
    Dim DestinationFile, CellValue, Filler_Char_To_Replace_Blanks As String
    Dim FileNum, ColumnCount, RowCount, FieldWidth As Integer
    Dim sht As Worksheet

    Dim outputRecord() As String
    'Below are options in case you want to change the folder where VBA stores the .txt file
    'We use ActiveWorkbook.Path in this example
    'ActiveWorkbook.Path 'the activeworkbook
    'ThisWorkbook.Path  'the workbook with the code
    'CurDir  'the current directory (when you hit File|open)

    'If a cell is blank, what character should be used instead
    Filler_Char_To_Replace_Blanks = "+"

    'Check if the user has made any selection at all
    If Selection.Cells.Count < 2 Then
        MsgBox "Nothing selected to export"
        Selection.Activate
        End
    End If

    'This is the destination file name.
    DestinationFile = ActiveWorkbook.Path & "\textfile.txt"  'This was changed to the DOS version of directory separator

    On Error GoTo catchFileOpenError    'Poor man's version of Try/Catch

    'Get a FileSystemObject using the MSFT Scripting Runtime reference
    Dim fd As Scripting.FileSystemObject
    Set fd = New Scripting.FileSystemObject

    Dim outputFile As Object
    Set outputFile = fd.CreateTextFile(DestinationFile, True, False)

    ' Turn error checking on.
    On Error GoTo 0

    Dim record As Scripting.Dictionary
    'Call a private function that gets the filed control information from the
    'Sheet titled FieldControl and the associated range
    Set record = GetFieldControl(ActiveWorkbook.Sheets("FieldControl").Range("A2:D7"))

    'Declare enumerators to loop through the selection
    Dim dataRow As Range
    Dim dataFld As Range

    'Declare the output buffer, 80 characters
    Dim outputBuffer(80) As Byte
    'loop thru the selection row by row
    For Each dataRow In Selection.Rows
        'Initialize buffer to empty value defined by the second parameter
        Call InitOutputBuffer(outputBuffer, Filler_Char_To_Replace_Blanks)
        'Loop thru each field in the row
        For Each dataFld In dataRow.Columns
            'Copy the input value into the output byte array
            Call CopyStringToByteArray(outputBuffer, StrConv(Trim(CStr(dataFld.Value2)), vbFromUnicode), _
                        record(dataFld.Column).StartPos, record(dataFld.Column).FieldType, record(dataFld.Column).Size)
        Next dataFld
        'Write the record to the text file but first convert ASCII Byte to Unicode String
        'Also this method places CR/LF as part of the output to the file
        outputFile.WriteLine StrConv(outputBuffer, vbUnicode)
    Next dataRow

     ' Close destination file.
    outputFile.Close

    Selection.Activate
    Workbooks.OpenText Filename:=DestinationFile
    Exit Sub

catchFileOpenError:     'Catch the error after trying if openning the file fails
    On Error GoTo 0
    MsgBox "Cannot open filename " & DestinationFile
    Selection.Activate
End Sub
'***********************************************************************************
'*
'* PARAMETERS:
'*  outBuf is the updated buffer
'*  inBuf is the input buffer that needs to be copied to the output buffer (buffer)
'*  startCol is the starting column for the field
'*  fldTy is the field type as defined by the class enumerator eFieldType
'*  fldLen is the length of the field as defined on the control sheet
Private Sub CopyStringToByteArray(ByRef outBuf() As Byte, ByRef inBuf() As Byte, _
                ByVal startCol As Long, ByRef fldTy As eFieldType, ByVal fldLen As Long)
    Dim idx As Long
    If fldTy = Text Then       'Left Justified
        For idx = LBound(inBuf) To UBound(inBuf)
            outBuf(startCol) = inBuf(idx)
            startCol = startCol + 1
        Next idx
    Else                        'Right Justified
        Dim revIdx As Long
        revIdx = startCol + fldLen - 1
        For idx = UBound(inBuf) To LBound(inBuf) Step -1
            outBuf(revIdx) = inBuf(idx)
            revIdx = revIdx - 1
        Next idx
    End If
End Sub
'***************************************************************************
'*  InitOutputBuffer
'*      PARAMETERS:
'*          buffer is the buffer to initialize
'*          initVal is a string containing the value used to initialize the buffer
Private Sub InitOutputBuffer(ByRef buffer() As Byte, ByVal initVal As String)
    Dim byInitVal() As Byte 'Byte array to hold the values from the string conversion
    byInitVal = StrConv(initVal, vbFromUnicode) 'convert the string into an ASCII array
    Dim idx As Long
    For idx = LBound(buffer) To UBound(buffer)
        buffer(idx) = byInitVal(0)
    Next idx

    'buffer(81) = Asc(Chr(13)) 'Carriage Return Character
    'buffer(82) = Asc(Chr(10)) 'Line Feed Character

End Sub

'*******************************************************************************
'*
'*  GetFieldControl
'*      PARAMETERS:
'*          ctrlRng is the range on a worksheet where the field control info is
'*              found
'*      REMARKS:
'*          The range needs to have the following columns: Name, Size, Start Postion
'*          and Type.  Type values can be Text or Number
Private Function GetFieldControl(ByRef ctrlRng As Range) As Scripting.Dictionary
    Dim retVal As Scripting.Dictionary
    Set retVal = New Scripting.Dictionary

    'format of control range is : Name, Size, Start Position, Type
    Dim fldInfoRow As Range
    Dim fld As clField  'A class that holds the control values from the work sheet
    Dim colCnt As Long: colCnt = 1  'Becomes the key for the dictionary
    For Each fldInfoRow In ctrlRng.Rows
        Set fld = New clField
        fld.Name = fldInfoRow.Value2(1, 1)      'Name of field in data table
        fld.Size = fldInfoRow.Value2(1, 2)      'Output Size of field
        fld.StartPos = fldInfoRow.Value2(1, 3)  'Output starting position for this field
        Select Case fldInfoRow.Value2(1, 4)     'Controls how the output value is formated
            Case "Text"                         '  Text left justified, Numbers are right justified
                fld.FieldType = Text
            Case "Number"
                fld.FieldType = Number
            Case Default
                fld.FieldType = Text
        End Select
        retVal.Add Key:=colCnt, Item:=fld   'Add the key and the fld object to the dictionary
        colCnt = colCnt + 1                 'This key value is mapped to the column number in the input data table
    Next fldInfoRow

    'Return the scripting Dictionary
    Set GetFieldControl = retVal
End Function

带有一些更新的模块

{{1}}