使用PHP解包Mainframe packed Decimal(BCD)

时间:2016-06-23 14:30:41

标签: php mainframe bcd packed-decimal

我从大型机获得了一个数据文件。我已经用PHP处理了EBCDIC到latin1的转换。但是现在剩下这些压缩的十进制字段。

例如,数字12345被打包成3个字节,看起来像:x' 12345C'

否定将是:x' 12345D'

所以右半边的字节告诉了标志。有没有办法用PHP轻松完成这个?

现在我这样做:

$bin = "\x12\x34\x5C";
var_dump(
    unpack("H*", $bin)
);

结果是:

array(1) {
  [1]=>
  string(4) "123c"
}

现在我可以检查最后一个标志是C还是D并且手动完成。但也许有更好的解决方案?

2 个答案:

答案 0 :(得分:2)

正如Bill所说,让大型机人员将文件转换为大型机上的文本并发送文本文件,排序等实用程序可以在大型机上执行此操作。也是只是压缩十进制在文件中还是你有二进制Zoned Decimal ???

如果您坚持在PHP中执行此操作,则需要在执行EBCDIC转换之前执行前的包装十进制转换,因为对于像x'400c这样的包装十进制 EBCDIC转换器将查看x'40'并说这是一个空格并将其转换为x'20',因此你的x'400c'变为x'200c'。

包装小数的最终nyble也可以是 f - 无符号以及c和d。

最后,如果你有Cobol Copybook,我的项目JRecord将Cobol改为Csv&& Cobol到Xml转换程序(用java编写)。参见

答案 1 :(得分:0)

好的,因为我没有找到任何更好的解决方案,我制作了一个用于处理此数据集记录的php类:

<?php
namespace Mainframe;

/**
 * Mainframe main function
 *
 * @author vp1zag4
 *        
 */
class Mainframe
{

    /**
     * Data string for reading
     * 
     * @var string | null
     */
    protected $data = null;

    /**
     * Default ouput charset
     * 
     * @var string
     */
    const OUTPUT_CHARSET = 'latin1';

    /**
     * Record length of dataset
     *
     * @var integer
     */
    protected $recordLength = 10;

    /**
     * Inits the
     *
     * @param unknown $data            
     */
    public function __construct($data = null)
    {
        if (! is_null($data)) {
            $this->setData($data);
        }
    }

    /**
     * Sets the data string and validates
     *
     * @param unknown $data            
     * @throws \LengthException
     */
    public function setData($data)
    {
        if (strlen($data) != $this->recordLength) {
            throw new \LengthException('Given data does not fit to dataset record length');
        }

        $this->data = $data;
    }

    /**
     * Unpack packed decimal (BCD) from mainframe format to integer
     *
     * @param unknown $str            
     * @return number
     */
    public static function unpackBCD($str)
    {
        $num = unpack('H*', $str);
        $num = array_shift($num);
        $sign = strtoupper(substr($num, - 1));
        $num = (int) substr($num, 0, - 1);
        if ($sign == 'D') {
            $num = $num * - 1;
        }
        return (int) $num;
    }

    /**
     * convert EBCDIC to default output charset
     *
     * @param string $str            
     * @return string
     */
    public static function conv($str, $optionalCharset = null)
    {
        $charset = (is_string($optionalCharset)) ? $optionalCharset : self::OUTPUT_CHARSET;
        return iconv('IBM037', $charset, $str);
    }

    /**
     * Reads part of data string and converts or unpacks
     *
     * @param integer $start
     * @param integer $length
     * @param bool $unpack
     * @param bool | string $conv
     */
    public function read($start, $length, $unpack = false, $conv = true)
    {
        if (empty($this->data)) {
            return null;
        }

        $result = substr($this->data, $start, $length);

        if($unpack) {
            return self::unpackBCD($result);
        }

        if ($conv) {
            return self::conv($result, $conv);
        }

        return $result;
    }
}

使用$ class-&gt; read(1,3,True),可以读取部分数据并同时转换/解压缩。

也许它会随时帮助任何人。

但是我当然会尝试设置一些Job,它将直接在大型机上以一些JSON数据作为输出为我做。