【问题标题】:C#: Convert COMP-3 Packed Decimal to Human-Readable ValueC#:将 COMP-3 压缩十进制转换为人类可读的值
【发布时间】:2010-09-13 15:59:25
【问题描述】:

我有一系列来自大型机的 ASCII 平面文件,由 C# 应用程序处理。引入了带有压缩十进制 (COMP-3) 字段的新提要,需要将其转换为数值。

文件正在通过 FTP 传输,使用 ASCII 传输模式。我担心二进制字段可能包含将被解释为非常低的 ASCII 代码或控制字符而不是值 - 或者更糟糕的是,可能会在 FTP 过程中丢失。

更重要的是,这些字段被读取为字符串。我可能可以灵活地解决这部分问题(即某种类型的流),但业务会拒绝我。

要求为“从 HEX 转换为 ASCII”,但显然这没有产生正确的值。任何帮助,将不胜感激;只要您能解释转换过程的逻辑,它就不必是特定于语言的。

【问题讨论】:

    标签: c# .net ibm-midrange flat-file comp-3


    【解决方案1】:

    我一直在许多板上观看有关将 Comp-3 BCD 数据从“旧”大型机文件转换为 C# 中可用的东西的帖子。首先,我想说的是,我对其中一些帖子收到的回复不太感兴趣——尤其是那些基本上说“你为什么用这些非 C#/C++ 相关的帖子来打扰我们”以及“如果您需要有关某种 COBOL 约定的答案,为什么不去访问一个面向 COBOL 的站点”。对我来说,这完全是废话,因为可能在未来很多年(不幸的是),软件开发人员都需要了解如何处理现实世界中存在的一些遗留问题。所以,即使我在这篇文章中因为以下代码而受到抨击,我也将与您分享我在 COMP-3/EBCDIC 转换方面必须处理的真实经历(是的,我就是谈论“软盘、纸带、光盘包等...... - 我从 1979 年开始就成为了一名软件工程师”)。

    首先 - 了解您从像 IBM 这样的传统大型机系统读取的任何文件都将以 EBCDIC 格式向您呈现数据,并且为了将任何数据转换为您可以处理的 C#/C++ 字符串您将不得不使用正确的代码页转换将数据转换为 ASCII 格式。如何处理这个问题的一个很好的例子是:

    StreamReader readFile = new StreamReader(path, Encoding.GetEncoding(037); // 037 = EBCDIC 到 ASCII 转换。

    这将确保您从此流中读取的任何内容都将被转换为 ASCII 并且可以以字符串格式使用。这包括 COBOL 声明的“分区十进制”(图 9)和“文本”(图 X)字段。但是,当读入 char[] 或 byte[] 数组时,这并不一定会将 COMP-3 字段转换为正确的“二进制”等效项。要做到这一点,你将得到正确翻译的唯一方法(即使使用 UTF-8、UTF-16、默认或其他)代码页,你会想要像这样打开文件:

    FileStream fileStream = new FileStream(path, FIleMode.Open, FIleAccess.Read, FileShare.Read);

    当然,“FileShare.Read”选项是“可选的”。

    当您隔离了要转换为十进制值的字段(如果需要,随后转换为 ASCII 字符串),您可以使用以下代码 - 这基本上是从 MicroSoft“UnpackDecimal”中窃取的您可以在以下位置发布的帖子:

    http://www.microsoft.com/downloads/details.aspx?familyid=0e4bba52-cc52-4d89-8590-cda297ff7fbd&displaylang=en

    我已经分离(我认为)这个逻辑中最重要的部分是什么,并将其合并为两种方法,您可以根据需要进行操作。出于我的目的,我选择将其保留为返回一个 Decimal 值,然后我可以用我想要的方式进行操作。基本上,该方法称为“解包”,您将一个 byte[] 数组(不超过 12 个字节)和作为 int 的比例传递给它,这是您希望在 Decimal 值中返回的小数位数。我希望这对你有用,对我也有用。

        private Decimal Unpack(byte[] inp, int scale)
        {
            long lo = 0;
            long mid = 0;
            long hi = 0;
            bool isNegative;
    
            // this nybble stores only the sign, not a digit.  
            // "C" hex is positive, "D" hex is negative, and "F" hex is unsigned. 
            switch (nibble(inp, 0))
            {
                case 0x0D:
                    isNegative = true;
                    break;
                case 0x0F:
                case 0x0C:
                    isNegative = false;
                    break;
                default:
                    throw new Exception("Bad sign nibble");
            }
            long intermediate;
            long carry;
            long digit;
            for (int j = inp.Length * 2 - 1; j > 0; j--)
            {
                // multiply by 10
                intermediate = lo * 10;
                lo = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                intermediate = mid * 10 + carry;
                mid = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                intermediate = hi * 10 + carry;
                hi = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                // By limiting input length to 14, we ensure overflow will never occur
    
                digit = nibble(inp, j);
                if (digit > 9)
                {
                    throw new Exception("Bad digit");
                }
                intermediate = lo + digit;
                lo = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                if (carry > 0)
                {
                    intermediate = mid + carry;
                    mid = intermediate & 0xffffffff;
                    carry = intermediate >> 32;
                    if (carry > 0)
                    {
                        intermediate = hi + carry;
                        hi = intermediate & 0xffffffff;
                        carry = intermediate >> 32;
                        // carry should never be non-zero. Back up with validation
                    }
                }
            }
            return new Decimal((int)lo, (int)mid, (int)hi, isNegative, (byte)scale);
        }
    
        private int nibble(byte[] inp, int nibbleNo)
        {
            int b = inp[inp.Length - 1 - nibbleNo / 2];
            return (nibbleNo % 2 == 0) ? (b & 0x0000000F) : (b >> 4);
        }
    

    如果您有任何问题,请在此处发布 - 因为我怀疑我会像其他选择发布与当今问题相关的问题的人一样被“激怒”...

    谢谢, 约翰 - 长老。

    【讨论】:

    • 如何读取文件并将其传递给 Unpack,我使用 filestream 打开文件,然后使用 StreamReader 读取每一行,然后将行转换为字节以传递给 Unpack 方法,直到流结束。 byte[] b = ASCIIEncoding.ASCII.GetBytes(reader.ReadLine());解包(b,0);但是在转换 13 位二进制格式数字的过程中给出了错误的值,如果我做错了什么,您能否建议如何读取文件并转换为字节并传递给解包。谢谢
    【解决方案2】:

    首先,您必须消除由 ASCII 传输模式引起的行尾 (EOL) 转换问题。当 BCD 值恰好对应 EOL 字符时,您担心数据损坏是绝对正确的。这个问题最糟糕的方面是它会很少和意外地发生。

    最好的解决办法是将传输模式更改为 BIN。这是合适的,因为您传输的数据是二进制的。如果无法使用正确的 FTP 传输模式,您可以在代码中撤消 ASCII 模式损坏。您所要做的就是将 \r\n 对转换回 \n。如果我是你,我会确保这是经过良好测试的。

    一旦您解决了 EOL 问题,COMP-3 转换就非常简单。我能够在 MS 知识库中找到 this article,并使用 BASIC 中的示例代码。有关此代码的 VB.NET 端口,请参见下文。

    由于您处理的是 COMP-3 值,因此您阅读的文件格式几乎肯定具有固定的记录大小和固定的字段长度。如果我是你,我会在你进一步研究之前获得文件格式规范。您应该使用 BinaryReader 来处理这些数据。如果有人反对这一点,我会走开。让他们找别人放纵自己的愚蠢。

    这是 BASIC 示例代码的 VB.NET 端口。我没有对此进行测试,因为我无权访问 COMP-3 文件。如果这不起作用,我会参考原始 MS 示例代码以获取指导,或参考此问题的其他答案中的参考。

    Imports Microsoft.VisualBasic
    
    Module Module1
    
    'Sample COMP-3 conversion code
    'Adapted from http://support.microsoft.com/kb/65323
    'This code has not been tested
    
    Sub Main()
    
        Dim Digits%(15)       'Holds the digits for each number (max = 16).
        Dim Basiceqv#(1000)   'Holds the Basic equivalent of each COMP-3 number.
    
        'Added to make code compile
        Dim MyByte As Char, HighPower%, HighNibble%
        Dim LowNibble%, Digit%, E%, Decimal%, FileName$
    
    
        'Clear the screen, get the filename and the amount of decimal places
        'desired for each number, and open the file for sequential input:
        FileName$ = InputBox("Enter the COBOL data file name: ")
        Decimal% = InputBox("Enter the number of decimal places desired: ")
    
        FileOpen(1, FileName$, OpenMode.Binary)
    
        Do Until EOF(1)   'Loop until the end of the file is reached.
            Input(1, MyByte)
            If MyByte = Chr(0) Then     'Check if byte is 0 (ASC won't work on 0).
                Digits%(HighPower%) = 0       'Make next two digits 0. Increment
                Digits%(HighPower% + 1) = 0   'the high power to reflect the
                HighPower% = HighPower% + 2   'number of digits in the number
                'plus 1.
            Else
                HighNibble% = Asc(MyByte) \ 16      'Extract the high and low
                LowNibble% = Asc(MyByte) And &HF    'nibbles from the byte. The
                Digits%(HighPower%) = HighNibble%  'high nibble will always be a
                'digit.
                If LowNibble% <= 9 Then                   'If low nibble is a
                    'digit, assign it and
                    Digits%(HighPower% + 1) = LowNibble%   'increment the high
                    HighPower% = HighPower% + 2            'power accordingly.
                Else
                    HighPower% = HighPower% + 1 'Low nibble was not a digit but a
                    Digit% = 0                  '+ or - signals end of number.
    
                    'Start at the highest power of 10 for the number and multiply
                    'each digit by the power of 10 place it occupies.
                    For Power% = (HighPower% - 1) To 0 Step -1
                        Basiceqv#(E%) = Basiceqv#(E%) + (Digits%(Digit%) * (10 ^ Power%))
                        Digit% = Digit% + 1
                    Next
    
                    'If the sign read was negative, make the number negative.
                    If LowNibble% = 13 Then
                        Basiceqv#(E%) = Basiceqv#(E%) - (2 * Basiceqv#(E%))
                    End If
    
                    'Give the number the desired amount of decimal places, print
                    'the number, increment E% to point to the next number to be
                    'converted, and reinitialize the highest power.
                    Basiceqv#(E%) = Basiceqv#(E%) / (10 ^ Decimal%)
                    Print(Basiceqv#(E%))
                    E% = E% + 1
                    HighPower% = 0
                End If
            End If
        Loop
    
        FileClose()   'Close the COBOL data file, and end.
    End Sub
    
    End Module
    

    【讨论】:

    • 二进制传输模式是否会对包含 ASCII 数据的文件的其余部分造成任何问题?上游进程将取消任何看起来比预先描述的长度短的行的资格,因此我可以推回并将其更改为明文数字。谢谢。
    • 实际上,该代码在 QBasic 中...我知道,但大约 10 年没有使用过 :) 我太生疏了,无法将其转换为 C#,让单独如何将其用于 ascii 文件中的 8 个字符的二进制字段...但感谢您的尝试。
    • 二进制 FTP 传输根本不会改变文件。
    • 如果这是公认的答案,你能告诉我们你最终是如何让它工作的吗?!我正在寻找完全相同的问题的解决方案并最终来到这里。请帮忙!!!我不懂QBasic 我太年轻了。
    • 我添加了一个 VB.NET 端口。我不确定这有多大帮助,但将旧的 QBASIC 代码移植到 VB.NET 3.5 是一个有趣的练习。令人惊讶的是,只需进行很少的更改即可使其编译和运行。但是,我会警惕输入语句,我不能保证它们会正确读取二进制文件。如果您有适当的测试文件,您应该能够验证代码的正确性(或不正确性)。
    【解决方案3】:

    如果原始数据在 EBCDIC 中,则您的 COMP-3 字段已乱码。 FTP 进程已经完成了 COMP-3 字段中字节值的 EBCDIC 到 ASCII 转换,这不是您想要的。要更正此问题,您可以:

    1) 使用 BINARY 模式进行传输,以便获得原始 EBCDIC 数据。然后将 COMP-3 字段转换为数字并将记录上的任何其他 EBCDIC 文本转换为 ASCII。压缩字段将每个数字存储在半字节中,下半字节作为符号(F 为正,其他值,通常 D 或 E,为负)。在 PIC 999.99 USAGE COMP-3 中存储 123.4 将为 X'01234F'(三个字节),而在同一字段中的 -123 为 X'01230D'。

    2) 让发件人将该字段转换为 USAGE IS DISPLAY SIGN IS LEADING(或 TRAILING)数字字段。这会将数字存储为 EBCDIC 数字字符串,符号为单独的负 (-) 或空白字符。在 FTP 传输中,所有数字和符号都正确转换为 ASCII 等价物。

    【讨论】:

    • 谢谢,我们已经处理好了。上游依赖项已更改平面文件以提供人类可读格式的数字。
    • 隐含小数呢?如:PIC 999v99 COMP-3
    • 隐含表示小数点不在存储值中。使用 PIC 999.99 USAGE IS DISPLAY SIGN LEADING,您将得到一个 -12345 字符串作为 -123.45 值或 b12345(前导空白)作为 123.45 正值。从 EBCDIC 到 ASCII 进行 FTP 时,数字文本确实得到正确转换。
    【解决方案4】:

    如果我在这里偏离了基础,我深表歉意,但也许我将粘贴在这里的这个代码示例可以帮助你。这来自 VBRocks...

    Imports System
    Imports System.IO
    Imports System.Text
    Imports System.Text.Encoding
    
    
    
    '4/20/07 submission includes a line spacing addition when a control character is used:
    '   The line spacing is calculated off of the 3rd control character.
    '
    '   Also includes the 4/18 modification of determining end of file.
    
    '4/26/07 submission inclues an addition of 6 to the record length when the 4th control
    '   character is an 8.  This is because these records were being truncated.
    
    
    'Authored by Gary A. Lima, aka. VBRocks
    
    
    
    ''' <summary>
    ''' Translates an EBCDIC file to an ASCII file.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class EBCDIC_to_ASCII_Translator
    
    #Region " Example"
    
        Private Sub Example()
            'Set your source file and destination file paths
            Dim sSourcePath As String = "c:\Temp\MyEBCDICFile"
            Dim sDestinationPath As String = "c:\Temp\TranslatedFile.txt"
    
            Dim trans As New EBCDIC_to_ASCII_Translator()
    
            'If your EBCDIC file uses Control records to determine the length of a record, then this to True
            trans.UseControlRecord = True
    
            'If the first record of your EBCDIC file is filler (junk), then set this to True
            trans.IgnoreFirstRecord = True
    
            'EBCDIC files are written in block lengths, set your block length (Example:  134, 900, Etc.)
            trans.BlockLength = 900
    
            'This method will actually translate your source file and output it to the specified destination file path
            trans.TranslateFile(sSourcePath, sDestinationPath)
    
    
            'Here is a alternate example:
            'No Control record is used
            'trans.UseControlRecord = False
    
            'Translate the whole file, including the first record
            'trans.IgnoreFirstRecord = False
    
            'Set the block length
            'trans.BlockLength = 134
    
            'Translate...
            'trans.TranslateFile(sSourcePath, sDestinationPath)
    
    
    
            '*** Some additional methods that you can use are:
    
            'Trim off leading characters from left side of string (position 0 to...)
            'trans.LTrim = 15
    
            'Translate 1 EBCDIC character to an ASCII character
            'Dim strASCIIChar as String = trans.TranslateCharacter("S")
    
            'Translate an EBCDIC character array to an ASCII string
            'trans.TranslateCharacters(chrEBCDICArray)
    
            'Translates an EBCDIC string to an ASCII string
            'Dim strASCII As String = trans.TranslateString("EBCDIC String")
    
    
        End Sub
    
    #End Region    'Example
    
        'Translate characters from EBCDIC to ASCII
    
        Private ASCIIEncoding As Encoding = Encoding.ASCII
        Private EBCDICEncoding As Encoding = Encoding.GetEncoding(37)  'EBCDIC
    
        'Block Length:  Can be fixed (Ex:  134). 
        Private miBlockLength As Integer = 0
        Private mbUseControlRec As Boolean = True        'If set to False, will return exact block length
        Private mbIgnoreFirstRecord As Boolean = True    'Will Ignore first record if set to true  (First record may be filler)
        Private miLTrim As Integer = 0
    
        ''' <summary>
        ''' Translates SourceFile from EBCDIC to ASCII.  Writes output to file path specified by DestinationFile parameter.
        ''' Set the BlockLength Property to designate block size to read.
        ''' </summary>
        ''' <param name="SourceFile">Enter the path of the Source File.</param>
        ''' <param name="DestinationFile">Enter the path of the Destination File.</param>
        ''' <remarks></remarks>
        Public Sub TranslateFile(ByVal SourceFile As String, ByVal DestinationFile As String)
    
            Dim iRecordLength As Integer     'Stores length of a record, not including the length of the Control Record (if used)
            Dim sRecord As String = ""         'Stores the actual record
            Dim iLineSpace As Integer = 1    'LineSpace:  1 for Single Space, 2 for Double Space, 3 for Triple Space...
    
            Dim iControlPosSix As Byte()      'Stores the 6th character of a Control Record (used to calculate record length)
            Dim iControlRec As Byte()          'Stores the EBCDIC Control Record (First 6 characters of record)
            Dim bEOR As Boolean                'End of Record Flag
            Dim bBOF As Boolean = True      'Beginning of file
            Dim iConsumedChars As Integer = 0     'Stores the number of consumed characters in the current block
            Dim bIgnoreRecord As Boolean = mbIgnoreFirstRecord   'Ignores the first record if set.
    
            Dim ControlArray(5) As Char         'Stores Control Record (first 6 bytes)
            Dim chrArray As Char()              'Stores characters just after read from file
    
            Dim sr As New StreamReader(SourceFile, EBCDICEncoding)
            Dim sw As New StreamWriter(DestinationFile)
    
            'Set the RecordLength to the RecordLength Property (below)
            iRecordLength = miBlockLength
    
            'Loop through entire file
            Do Until sr.EndOfStream = True
    
                'If using a Control Record, then check record for valid data.
                If mbUseControlRec = True Then
                    'Read the Control Record (first 6 characters of the record)
                    sr.ReadBlock(ControlArray, 0, 6)
    
                    'Update the value of consumed (read) characters
                    iConsumedChars += ControlArray.Length
    
                    'Get the bytes of the Control Record Array
                    iControlRec = EBCDICEncoding.GetBytes(ControlArray)
    
                    'Set the line spacing  (position 3 divided by 64)
                    '   (64 decimal = Single Spacing; 128 decimal = Double Spacing)
                    iLineSpace = iControlRec(2) / 64
    
    
                    'Check the Control record for End of File
                    'If the Control record has a 8 or 10 in position 1, and a 1 in postion 2, then it is the end of the file
                    If (iControlRec(0) = 8 OrElse iControlRec(0) = 10) AndAlso _
                        iControlRec(1) = 1 Then
    
                        If bBOF = False Then
                            Exit Do
    
                        Else
                            'The Beginning of file flag is set to true by default, so when the first
                            '   record is encountered, it is bypassed and the bBOF flag is set to False
                            bBOF = False
    
                        End If    'If bBOF = Fals
    
                    End If    'If (iControlRec(0) = 8 OrElse
    
    
    
                    'Set the default value for the End of Record flag to True
                    '   If the Control Record has all zeros, then it's True, else False
                    bEOR = True
    
                    'If the Control record contains all zeros, bEOR will stay True, else it will be set to False
                    For i As Integer = 0 To 5
                        If iControlRec(i) > 0 Then
                            bEOR = False
    
                            Exit For
    
                        End If    'If iControlRec(i) > 0
    
                    Next    'For i As Integer = 0 To 5
    
                    If bEOR = False Then
                        'Convert EBCDIC character to ASCII
                        'Multiply the 6th byte by 6 to get record length
                        '   Why multiply by 6?  Because it works.
                        iControlPosSix = EBCDICEncoding.GetBytes(ControlArray(5))
    
                        'If the 4th position of the control record is an 8, then add 6
                        '    to the record length to pick up remaining characters.
                        If iControlRec(3) = 8 Then
                            iRecordLength = CInt(iControlPosSix(0)) * 6 + 6
    
                        Else
                            iRecordLength = CInt(iControlPosSix(0)) * 6
    
                        End If
    
                        'Add the length of the record to the Consumed Characters counter
                        iConsumedChars += iRecordLength
    
                    Else
                        'If the Control Record had all zeros in it, then it is the end of the Block.
    
                        'Consume the remainder of the block so we can continue at the beginning of the next block.
                        ReDim chrArray(miBlockLength - iConsumedChars - 1)
                        'ReDim chrArray(iRecordLength - iConsumedChars - 1)
    
                        'Consume (read) the remaining characters in the block.  
                        '   We are not doing anything with them because they are not actual records.
                        'sr.ReadBlock(chrArray, 0, iRecordLength - iConsumedChars)
                        sr.ReadBlock(chrArray, 0, miBlockLength - iConsumedChars)
    
                        'Reset the Consumed Characters counter
                        iConsumedChars = 0
    
                        'Set the Record Length to 0 so it will not be processed below.
                        iRecordLength = 0
    
                    End If    ' If bEOR = False
    
                End If    'If mbUseControlRec = True
    
    
    
                If iRecordLength > 0 Then
                    'Resize our array, dumping previous data.  Because Arrays are Zero (0) based, subtract 1 from the Record length.
                    ReDim chrArray(iRecordLength - 1)
    
                    'Read the specfied record length, without the Control Record, because we already consumed (read) it.
                    sr.ReadBlock(chrArray, 0, iRecordLength)
    
                    'Copy Character Array to String Array, Converting in the process, then Join the Array to a string
                    sRecord = Join(Array.ConvertAll(chrArray, New Converter(Of Char, String)(AddressOf ChrToStr)), "")
    
                    'If the record length was 0, then the Join method may return Nothing
                    If IsNothing(sRecord) = False Then
    
                        If bIgnoreRecord = True Then
                            'Do nothing - bypass record
    
                            'Reset flag
                            bIgnoreRecord = False
    
                        Else
                            'Write the line out, LTrimming the specified number of characters.
                            If sRecord.Length >= miLTrim Then
                                sw.WriteLine(sRecord.Remove(0, miLTrim))
    
                            Else
                                sw.WriteLine(sRecord.Remove(0, sRecord.Length))
    
                            End If    ' If sRecord.Length >= miLTrim
    
                            'Write out the number of blank lines specified by the 3rd control character.
                            For i As Integer = 1 To iLineSpace - 1
                                sw.WriteLine("")
    
                            Next    'For i As Integer = 1 To iLineSpace
    
                        End If    'If bIgnoreRecord = True
    
    
                        'Obviously, if we have read more characters from the file than the designated size of the block,
                        '   then subtract the number of characters we have read into the next block from the block size.
                        If iConsumedChars > miBlockLength Then
                            'If iConsumedChars > iRecordLength Then
                            iConsumedChars = iConsumedChars - miBlockLength
                            'iConsumedChars = iConsumedChars - iRecordLength
    
                        End If
    
                    End If    'If IsNothing(sRecord) = False
    
                End If    'If iRecordLength > 0
    
                'Allow computer to process  (works in a class module, not in a dll)
                'Application.DoEvents()
    
            Loop
    
            'Destroy StreamReader (sr)
            sr.Close()
            sr.Dispose()
    
            'Destroy StreamWriter (sw)
            sw.Close()
            sw.Dispose()
    
        End Sub
    
    
    
        ''' <summary>
        ''' Translates 1 EBCDIC Character (Char) to an ASCII String
        ''' </summary>
        ''' <param name="chr"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ChrToStr(ByVal chr As Char) As String
            Dim sReturn As String = ""
    
            'Convert character into byte
            Dim EBCDICbyte As Byte() = EBCDICEncoding.GetBytes(chr)
    
            'Convert EBCDIC byte to ASCII byte
            Dim ASCIIByte As Byte() = Encoding.Convert(EBCDICEncoding, ASCIIEncoding, EBCDICbyte)
    
            sReturn = Encoding.ASCII.GetString(ASCIIByte)
    
            Return sReturn
    
        End Function
    
    
    
        ''' <summary>
        ''' Translates an EBCDIC String to an ASCII String
        ''' </summary>
        ''' <param name="sStringToTranslate"></param>
        ''' <returns>String</returns>
        ''' <remarks></remarks>
        Public Function TranslateString(ByVal sStringToTranslate As String) As String
            Dim i As Integer = 0
            Dim sReturn As New System.Text.StringBuilder()
    
            'Loop through the string and translate each character
            For i = 0 To sStringToTranslate.Length - 1
                sReturn.Append(ChrToStr(sStringToTranslate.Substring(i, 1)))
    
            Next
    
            Return sReturn.ToString()
    
    
        End Function
    
    
    
        ''' <summary>
        ''' Translates 1 EBCDIC Character (Char) to an ASCII String
        ''' </summary>
        ''' <param name="sCharacterToTranslate"></param>
        ''' <returns>String</returns>
        ''' <remarks></remarks>
        Public Function TranslateCharacter(ByVal sCharacterToTranslate As Char) As String
    
            Return ChrToStr(sCharacterToTranslate)
    
        End Function
    
    
    
        ''' <summary>
        ''' Translates an EBCDIC Character (Char) Array to an ASCII String
        ''' </summary>
        ''' <param name="sCharacterArrayToTranslate"></param>
        ''' <returns>String</returns>
        ''' <remarks>Remarks</remarks>
        Public Function TranslateCharacters(ByVal sCharacterArrayToTranslate As Char()) As String
            Dim sReturn As String = ""
    
            'Copy Character Array to String Array, Converting in the process, then Join the Array to a string
            sReturn = Join(Array.ConvertAll(sCharacterArrayToTranslate, _
                                New Converter(Of Char, String)(AddressOf ChrToStr)), "")
    
            Return sReturn
    
        End Function
    
    
        ''' <summary>
        ''' Block Length must be set.  You can set the BlockLength for specific block sizes (Ex:  134).
        ''' Set UseControlRecord = False for files with specific block sizes (Default is True)
        ''' </summary>
        ''' <value>0</value>
        ''' <returns>Integer</returns>
        ''' <remarks></remarks>
        Public Property BlockLength() As Integer
            Get
                Return miBlockLength
    
            End Get
            Set(ByVal value As Integer)
                miBlockLength = value
    
            End Set
        End Property
    
    
    
        ''' <summary>
        ''' Determines whether a ControlKey is used to calculate RecordLength of valid data
        ''' </summary>
        ''' <value>Default value is True</value>
        ''' <returns>Boolean</returns>
        ''' <remarks></remarks>
        Public Property UseControlRecord() As Boolean
            Get
                Return mbUseControlRec
    
            End Get
            Set(ByVal value As Boolean)
                mbUseControlRec = value
    
            End Set
        End Property
    
    
    
        ''' <summary>
        ''' Ignores first record if set (Default is True)
        ''' </summary>
        ''' <value>Default is True</value>
        ''' <returns>Boolean</returns>
        ''' <remarks></remarks>
        Public Property IgnoreFirstRecord() As Boolean
            Get
                Return mbIgnoreFirstRecord
    
            End Get
    
            Set(ByVal value As Boolean)
                mbIgnoreFirstRecord = value
    
            End Set
        End Property
    
    
    
        ''' <summary>
        ''' Trims the left side of every string the specfied number of characters.  Default is 0.
        ''' </summary>
        ''' <value>Default is 0.</value>
        ''' <returns>Integer</returns>
        ''' <remarks></remarks>
        Public Property LTrim() As Integer
            Get
                Return miLTrim
    
            End Get
    
            Set(ByVal value As Integer)
                miLTrim = value
    
            End Set
        End Property
    
    
    End Class
    

    【讨论】:

    • 哇,谢谢。我不会为大粘贴而叮你(这毕竟是一个答案网站)......投票支持一个看起来正确的快速答案 - 但我不能接受它,直到我回去星期一上班。幸运的是我也知道 VB.NET...谢谢。
    • 不,没有这样做。给定明文数字或 COMP-3 数据,它会输出一堆垃圾。根据我的样本数据,我需要将“
    【解决方案5】:

    一些有用的EBCDIC翻译链接:

    转换表 - 检查压缩十进制字段中的一些值很有用: http://www.simotime.com/asc2ebc1.htm

    msdn中的代码页列表:
    http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx

    还有一段代码在C#中转换字节数组字段:

    // 500 is the code page for IBM EBCDIC International 
    System.Text.Encoding enc = new System.Text.Encoding(500);
    string value = enc.GetString(byteArrayField);
    

    【讨论】:

      【解决方案6】:

      压缩字段在 EBCDIC 或 ASCII 中是相同的。不要在它们上运行 EBCDIC 到 ASCII 的转换。在 .Net 中将它们转储为 byte[]。

      您使用按位掩码和移位来打包/解包。 -- 但按位运算仅适用于 .Net 中的整数类型,因此您需要跳过一些环节!

      优秀的 COBOL 或 C 艺术家可以为您指明正确的方向。

      找到一个老家伙并支付你的会费(大约三杯啤酒应该这样做)。

      【讨论】:

        【解决方案7】:

        “ASCII 传输类型”会将文件作为常规文本文件传输。因此,当我们以 ASCII 传输类型传输压缩十进制或二进制数据文件时,文件会损坏。 “二进制传输类型”将以二进制模式传输数据,将文件作为二进制数据而不是文本数据处理。所以我们必须在这里使用二进制传输类型。 参考:https://www.codeproject.com/Tips/673240/EBCDIC-to-ASCII-Converter

        一旦你的文件准备好了,这里就是将压缩十进制转换为人类可读的十进制的代码。

            using System;
            using System.Collections.Generic;
            using System.IO;
            using System.Linq;
            using System.Text;
            using System.Threading.Tasks;
        
           namespace ConsoleApp2
           {
            class Program
            {
                static void Main(string[] args)
                {
                    var path = @"C:\FileName.BIN.dat";
                    var templates = new List<Template>
                    {
                        new Template{StartPos=1,CharLength=4,Type="AlphaNum"},
                        new Template{StartPos=5,CharLength=1,Type="AlphaNum"},
                        new Template{StartPos=6,CharLength=8,Type="AlphaNum"},
                        new Template{StartPos=14,CharLength=1,Type="AlphaNum"},
                        new Template{StartPos=46,CharLength=4,Type="Packed",DecimalPlace=2},
                        new Template{StartPos=54,CharLength=5,Type="Packed",DecimalPlace=0},
                        new Template{StartPos=60,CharLength=4,Type="Packed",DecimalPlace=2},
                        new Template{StartPos=64,CharLength=1,Type="AlphaNum"}
                    };
        
                    var allBytes = File.ReadAllBytes(path);
                    for (int i = 0; i < allBytes.Length; i += 66)
                    {
                        var IsLastline = (allBytes.Length - i) < 66;
                        var lineLength = IsLastline ? 64 : 66;
                        byte[] lineBytes = new byte[lineLength];
                        Array.Copy(allBytes, i, lineBytes, 0, lineLength);
        
        
                        var outArray = new string[templates.Count];
                        int index = 0;
                        foreach (var temp in templates)
                        {
                            byte[] amoutBytes = new byte[temp.CharLength];
                            Array.Copy(lineBytes, temp.StartPos - 1, amoutBytes, 0, 
            temp.CharLength);
                            var final = "";
                            if (temp.Type == "Packed")
                            {
                                final = Unpack(amoutBytes, temp.DecimalPlace).ToString();
                            }
                            else
                            {
                                final = ConvertEbcdicString(amoutBytes);
                            }
        
                            outArray[index] = final;
                            index++;
        
                        }
        
                        Console.WriteLine(string.Join(" ", outArray));
        
                    }
        
                    Console.ReadLine();
                }
        
        
                private static string ConvertEbcdicString(byte[] ebcdicBytes)
                {
                    if (ebcdicBytes.All(p => p == 0x00 || p == 0xFF))
                    {
                        //Every byte is either 0x00 or 0xFF (fillers)
                        return string.Empty;
                    }
        
                    Encoding ebcdicEnc = Encoding.GetEncoding("IBM037");
                    string result = ebcdicEnc.GetString(ebcdicBytes); // convert EBCDIC Bytes -> 
            Unicode string
                    return result;
                }
        
                private static Decimal Unpack(byte[] inp, int scale)
                {
                    long lo = 0;
                    long mid = 0;
                    long hi = 0;
                    bool isNegative;
        
                    // this nybble stores only the sign, not a digit.  
                    // "C" hex is positive, "D" hex is negative, AlphaNumd "F" hex is unsigned. 
                    var ff = nibble(inp, 0);
                    switch (ff)
                    {
                        case 0x0D:
                            isNegative = true;
                            break;
                        case 0x0F:
                        case 0x0C:
                            isNegative = false;
                            break;
                        default:
                            throw new Exception("Bad sign nibble");
                    }
                    long intermediate;
                    long carry;
                    long digit;
                    for (int j = inp.Length * 2 - 1; j > 0; j--)
                    {
                        // multiply by 10
                        intermediate = lo * 10;
                        lo = intermediate & 0xffffffff;
                        carry = intermediate >> 32;
                        intermediate = mid * 10 + carry;
                        mid = intermediate & 0xffffffff;
                        carry = intermediate >> 32;
                        intermediate = hi * 10 + carry;
                        hi = intermediate & 0xffffffff;
                        carry = intermediate >> 32;
                        // By limiting input length to 14, we ensure overflow will never occur
        
                        digit = nibble(inp, j);
                        if (digit > 9)
                        {
                            throw new Exception("Bad digit");
                        }
                        intermediate = lo + digit;
                        lo = intermediate & 0xffffffff;
                        carry = intermediate >> 32;
                        if (carry > 0)
                        {
                            intermediate = mid + carry;
                            mid = intermediate & 0xffffffff;
                            carry = intermediate >> 32;
                            if (carry > 0)
                            {
                                intermediate = hi + carry;
                                hi = intermediate & 0xffffffff;
                                carry = intermediate >> 32;
                                // carry should never be non-zero. Back up with validation
                            }
                        }
                    }
                    return new Decimal((int)lo, (int)mid, (int)hi, isNegative, (byte)scale);
                }
        
                private static int nibble(byte[] inp, int nibbleNo)
                {
                    int b = inp[inp.Length - 1 - nibbleNo / 2];
                    return (nibbleNo % 2 == 0) ? (b & 0x0000000F) : (b >> 4);
                }
        
                class Template
                {
                    public string Name { get; set; }
                    public string Type { get; set; }
                    public int StartPos { get; set; }
                    public int CharLength { get; set; }
                    public int DecimalPlace { get; set; }
                }
            }
           }
        

        【讨论】:

          【解决方案8】:

          文件必须以二进制形式传输。这是一个更短的方法:

          using System.Linq;
          
          namespace SomeNamespace
          {
              public static class SomeExtensionClass
              {
                  /// <summary>
                  /// computes the actual decimal value from an IBM "Packed Decimal" 9(x)v9 (COBOL) format
                  /// </summary>
                  /// <param name="value">byte[]</param>
                  /// <param name="precision">byte; decimal places, default 2</param>
                  /// <returns>decimal</returns>
                  public static decimal FromPackedDecimal(this byte[] value, byte precision = 2)
                  {
                      if (value.Length < 1)
                      {
                          throw new System.InvalidOperationException("Cannot unpack empty bytes.");
                      }
                      double power = System.Math.Pow(10, precision);
                      if (power > long.MaxValue)
                      {
                          throw new System.InvalidOperationException(
                              $"Precision too large for valid calculation: {precision}");
                      }
                      string hex = System.BitConverter.ToString(value).Replace("-", "");
                      var bytes = Enumerable.Range(0, hex.Length)
                               .Select(x => System.Convert.ToByte($"0{hex.Substring(x, 1)}", 16))
                               .ToList();
                      long place = 1;
                      decimal ret = 0;
                      for (int i = bytes.Count - 2; i > -1; i--)
                      {
                          ret += (bytes[i] * place);
                          place *= 10;
                      }
                      ret /= (long)power;
                      return (bytes.Last() & (1 << 7)) != 0 ? ret * -1 : ret;
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-01-22
            • 2023-04-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-06-07
            • 1970-01-01
            相关资源
            最近更新 更多