Распаковать упакованный мэйнфрейм десятичный (BCD) с переполнением стека

Я получил файл данных с мэйнфрейма. Я уже занимался преобразованием EBCDIC в latin1 с помощью PHP. Но теперь остались эти упакованные десятичные поля.

Например, число 12345 упаковано в 3 байта и выглядит так: x’12345C ‘

Отрицательный будет как: x’12345D ‘

Таким образом, правая половина Байт говорит знак. Есть ли способ сделать это легко с PHP?

Теперь я делаю это как:

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

Это приводит к:

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

Теперь я могу проверить, является ли последний знак C или D, и сделать все вручную. Но, может быть, есть лучшее решение?

3

Решение

Как сказал Билл, заставьте людей из мэйнфреймов конвертировать файл в Текст на мейнфрейме и отправьте текстовый файл, утилиты вроде sort и т. д. могут сделать это на мэйнфрейме. И это только что упакованный десятичный в файле или у вас есть либо двоичный или же Десятичная зона также ???

Если вы настаиваете на том, чтобы сделать это в PHP, вам нужно выполнить преобразование десятичного числа в упакованном виде. до вы делаете преобразование EBCDIC, потому что для упакованного десятичного числа, как x’400c ‘
конвертер EBCDIC будет смотреть на x’40 ‘и говорить, что это пробел, и конвертировать его в x’20’, так что ваш x’400c ‘становится x’200c’.

Кроме того, окончательный nyble в упакованном десятичном виде может быть f — без знака а также c и d.

Наконец, если у вас есть тетрадь Cobol, мой проект JRecord имеет Cobol в CSV && Программы преобразования Cobol в Xml (написано на Java). Увидеть

2

Другие решения

Хорошо, поскольку я не нашел более подходящего решения, я создал 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-> read (1, 3, True) можно читать часть данных и конвертировать / распаковывать их одновременно.

Может быть, это тоже кому-нибудь поможет в любое время.

Но, конечно, я постараюсь настроить некоторые задания, которые будут делать это непосредственно для меня на мэйнфрейме с некоторыми данными JSON в качестве вывода.

0

По вопросам рекламы [email protected]