使用 PHP 解压大型机压缩十进制 (BCD)
Unpack Mainframe packed Decimal (BCD) with PHP
我从大型机上得到了一个数据文件。我已经使用 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,然后全部手动完成。但也许有更好的解决方案?
正如 Bill 所说,让大型机人员将文件转换为 大型机上的文本 并发送文本文件,排序等实用程序可以在大型机上执行此操作。它也是只是在文件中打包了十进制还是你有二进制或Zoned Decimal???
如果您坚持在 PHP 中执行此操作,则需要在 进行 EBCDIC 转换之前 进行压缩十进制转换,因为对于像 x 这样的压缩十进制'400c'
EBCDIC 转换器将查看 x'40' 并说这是一个 space 并将其转换为 x'20',因此您的 x'400c' 变为 x'200c'。
此外,压缩十进制中的最后一个 nyble 可以是 f - unsigned 以及 c 和 d。
最后,如果你有 Cobol Copybook,我的项目 JRecord 有 Cobol 到 Csv && Cobol 到 Xml 的转换程序(写在 java 中)。参见
好的,因为我没有找到任何更好的解决方案,所以我做了一个 php-class 来处理来自这个数据集的记录:
<?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->read(1, 3, True) 可以同时读取部分数据和 convert/unpack。
也许它也会随时帮助任何人。
但我当然会尝试设置一些 Job,它会直接在大型机上为我完成这些工作,并输出一些 JSON 数据。
我从大型机上得到了一个数据文件。我已经使用 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,然后全部手动完成。但也许有更好的解决方案?
正如 Bill 所说,让大型机人员将文件转换为 大型机上的文本 并发送文本文件,排序等实用程序可以在大型机上执行此操作。它也是只是在文件中打包了十进制还是你有二进制或Zoned Decimal???
如果您坚持在 PHP 中执行此操作,则需要在 进行 EBCDIC 转换之前 进行压缩十进制转换,因为对于像 x 这样的压缩十进制'400c' EBCDIC 转换器将查看 x'40' 并说这是一个 space 并将其转换为 x'20',因此您的 x'400c' 变为 x'200c'。
此外,压缩十进制中的最后一个 nyble 可以是 f - unsigned 以及 c 和 d。
最后,如果你有 Cobol Copybook,我的项目 JRecord 有 Cobol 到 Csv && Cobol 到 Xml 的转换程序(写在 java 中)。参见
好的,因为我没有找到任何更好的解决方案,所以我做了一个 php-class 来处理来自这个数据集的记录:
<?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->read(1, 3, True) 可以同时读取部分数据和 convert/unpack。
也许它也会随时帮助任何人。
但我当然会尝试设置一些 Job,它会直接在大型机上为我完成这些工作,并输出一些 JSON 数据。