Подтвердить что ты не робот

Библиотека PHP для создания/обработки текстовых файлов фиксированной ширины

У нас есть веб-приложение, которое отслеживает время, зарплату и HR. В результате мы должны написать много файлов данных фиксированной ширины для экспорта в другие системы (государственные налоговые документы, файлы ACH и т.д.). Кто-нибудь знает хорошую библиотеку для этого, где вы можете определить типы/структуры записи, а затем действовать на них в парадигме ООП?

Идея была бы классом, который вы передадите спецификациям, а затем работать с экземпляром указанной спецификации. IE:

$icesa_file = new FixedWidthFile();
$icesa_file->setSpecification('icesa.xml');
$icesa_file->addEmployer( $some_data_structure );

Где icesa.xml - это файл, содержащий спецификацию, хотя вы могли бы просто использовать вызовы ООП для его определения:

$specification = new FixedWidthFileSpecification('ICESA');
$specification->addRecordType(
    $record_type_name = 'Employer',
    $record_fields = array(
         array('Field Name', Width, Vailditation Type, options)
         )
     );

РЕДАКТИРОВАТЬ: Я не ищу совета о том, как писать такую ​​библиотеку. Я просто хотел знать, существует ли она уже. Спасибо!

4b9b3361

Ответ 1

Я не знаю о библиотеке, которая делает именно то, что вы хотите, но должно быть довольно просто перевернуть свои собственные классы, которые справятся с этим. Предполагая, что вы в основном заинтересованы в записи данных в этих форматах, я бы использовал следующий подход:

(1) Напишите легкий класс форматирования для строк фиксированной ширины. Он должен поддерживать определенные пользователем типы записей и должен быть гибким в отношении разрешенных форматов.

(2) Создайте этот класс для каждого используемого формата файла и добавьте необходимые типы записей

(3) Используйте этот форматтер для форматирования данных

Как вы предположили, вы можете определить типы записей в XML и загрузить этот файл XML на этапе (2). Я не знаю, насколько вы опытны с XML, но по моему опыту форматы XML часто вызывают много головных болей (возможно, из-за моей собственной некомпетентности в отношении XML). Если вы собираетесь использовать эти классы только в своей программе PHP, вам нечего выиграть от определения вашего формата в XML. Использование XML - хороший вариант, если вам нужно будет использовать определения формата файла во многих других приложениях.

Чтобы проиллюстрировать мои идеи, вот как я думаю, вы бы использовали этот предложенный класс форматирования:

<?php
include 'FixedWidthFormatter.php' // contains the FixedWidthFormatter class
include 'icesa-format-declaration.php' // contains $icesaFormatter
$file = fopen("icesafile.txt", "w");

fputs ($file, $icesaFormatter->formatRecord( 'A-RECORD', array( 
    'year' => 2011, 
    'tein' => '12-3456789-P',
    'tname'=> 'Willie Nelson'
)));
// output: A2011123456789UTAX     Willie Nelson                                     

// etc...

fclose ($file);
?>

Файл icesa-format-declaration.php может содержать объявление формата так или иначе:

<?php
$icesaFormatter = new FixedWidthFormatter();
$icesaFormatter->addRecordType( 'A-RECORD', array(
    // the first field is the record identifier
    // for A records, this is simply the character A
    'record-identifier' => array(
        'value' => 'A',  // constant string
        'length' => 1 // not strictly necessary
                      // used for error checking
    ),
    // the year is a 4 digit field
    // it can simply be formatted printf style
    // sourceField defines which key from the input array is used
    'year' =>  array(
        'format' => '% -4d',  // 4 characters, left justified, space padded
        'length' => 4,
        'sourceField' => 'year'
    ),
    // the EIN is a more complicated field
    // we must strip hyphens and suffixes, so we define
    // a closure that performs this formatting
    'transmitter-ein' => array(
        'formatter'=> function($EIN){
            $cleanedEIN =  preg_replace('/\D+/','',$EIN); // remove anything that not a digit
            return sprintf('% -9d', $cleanedEIN); // left justified and padded with blanks
        },
        'length' => 9,
        'sourceField' => 'tein'
    ),
    'tax-entity-code' => array(
        'value' => 'UTAX',  // constant string
        'length' => 4
    ),
    'blanks' => array(
        'value' => '     ',  // constant string
        'length' => 5
    ),
    'transmitter-name' =>  array(
        'format' => '% -50s',  // 50 characters, left justified, space padded
        'length' => 50,
        'sourceField' => 'tname'
    ),
    // etc. etc.
));
?>

Тогда вам нужен только класс FixedWidthFormatter, который может выглядеть так:

<?php

class FixedWidthFormatter {

    var $recordTypes = array();

    function addRecordType( $recordTypeName, $recordTypeDeclaration ){
        // perform some checking to make sure that $recordTypeDeclaration is valid
        $this->recordTypes[$recordTypeName] = $recordTypeDeclaration;
    }

    function formatRecord( $type, $data ) {
        if (!array_key_exists($type, $this->recordTypes)) {
            trigger_error("Undefinded record type: '$type'");
            return "";
        }
        $output = '';
        $typeDeclaration = $this->recordTypes[$type];
        foreach($typeDeclaration as $fieldName => $fieldDeclaration) {
            // there are three possible field variants:
            //  - constant fields
            //  - fields formatted with printf
            //  - fields formatted with a custom function/closure
            if (array_key_exists('value',$fieldDeclaration)) {
                $value = $fieldDeclaration['value'];
            } else if (array_key_exists('format',$fieldDeclaration)) {
                $value = sprintf($fieldDeclaration['format'], $data[$fieldDeclaration['sourceField']]);
            } else if (array_key_exists('formatter',$fieldDeclaration)) {
                $value = $fieldDeclaration['formatter']($data[$fieldDeclaration['sourceField']]);
            } else {
                trigger_error("Invalid field declaration for field '$fieldName' record type '$type'");
                return '';
            }

            // check if the formatted value has the right length
            if (strlen($value)!=$fieldDeclaration['length']) {
                trigger_error("The formatted value '$value' for field '$fieldName' record type '$type' is not of correct length ({$fieldDeclaration['length']}).");
                return '';
            }
            $output .= $value;
        }
        return $output . "\n";
    }
}


?>

Если вам нужна поддержка чтения, класс Formatter может быть расширен и для чтения, но это может выходить за рамки этого ответа.

Ответ 2

Я с радостью использовал этот класс для аналогичного использования раньше. Это файл php-классов, но он очень хорошо оценен и много проверен и проверен. Это не ново (2003), но, несмотря на то, что он по-прежнему выполняет очень хорошую работу +, имеет очень приличный и чистый API, который выглядит несколько как пример, который вы опубликовали с добавлением многих других положительных героев.

Если вы можете игнорировать немецкое использование в примерах, а возрастный фактор → это очень приличный фрагмент кода.

Posted from the example:


//CSV-Datei mit Festlängen-Werten 
echo "<p>Import aus der Datei fixed.csv</p>"; 
$csv_import2 = new CSVFixImport; 
$csv_import2->setFile("fixed.csv"); 
$csv_import2->addCSVField("Satzart", 2); 
$csv_import2->addCSVField("Typ", 1); 
$csv_import2->addCSVField("Gewichtsklasse", 1); 
$csv_import2->addCSVField("Marke", 4); 
$csv_import2->addCSVField("interne Nummer", 4); 


$csv_import2->addFilter("Satzart", "==", "020"); 
$csv_import2->parseCSV(); 
if($csv_import->isOK()) 
{ 
    echo "Anzahl der Datensätze: <b>" . $csv_import2->CSVNumRows() . "</b><br>"; 
    echo "Anzahl der Felder: <b>" . $csv_import2->CSVNumFields() . "</b><br>"; 
    echo "Name des 1.Feldes: <b>" . $csv_import2->CSVFieldName(0) . "</b><br>"; 

    $csv_import2->dumpResult(); 
}

Мои 2 цента, удачи!

Ответ 3

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

Посмотрите Zend_Filter и компоненты Zend_Validate из Zend Framework. Я думаю, что оба компонента довольно автономны и требуют только Zend_Loader. Если вы хотите, вы можете вытащить только эти три компонента из Zend Framework и удалить оставшуюся часть.

Zend_Filter_Input действует как набор фильтров и валидаторов. Вы определяете набор фильтров и валидаторов для каждого поля записи данных, которое вы можете использовать для обработки каждой записи набора данных. Есть много полезных фильтров и валидаторов, которые уже определены, а интерфейс для написания собственного достаточно прост. Я предлагаю фильтр StringTrim для удаления дополняющих символов.

Чтобы разбить каждую строку на поля, я бы расширил класс Zend_Filter_Input и добавил метод setDataFromFixedWidth(), например:

class My_Filter_Input extends Zend_Filter_Input
{
    public function setDataFromFixedWidth($record, array $recordRules)
    {
        if (array_key_exists('regex', $recordRules) {
            $recordRules = array($recordRules);
        }

        foreach ($recordRules as $rule) {
            $matches = array();
            if (preg_match($rule['regex'], $record, $matches)) {
                $data = array_combine($rule['fields'], $matches);
                return $this->setData($data);
            }
        }

        return $this->setData(array());
    }

}

И определите различные типы записей с помощью простых регулярных выражений и совпадающих имен полей. ICESA может выглядеть примерно так:

$recordRules = array(
    array(
        'regex'  => '/^(A)(.{4})(.{9})(.{4})/',  // This is only the first four fields, obviously
        'fields' => array('recordId', 'year', 'federalEin', 'taxingEntity',),
    ),
    array(
        'regex'  => '/^(B)(.{4})(.{9})(.{8})/',
        'fields' => array('recordId', 'year', 'federalEin', 'computer',),
    ),
    array(
        'regex'  => '/^(E)(.{4})(.{9})(.{9})/',
        'fields' => array('recordId', 'paymentYear', 'federalEin', 'blank1',),
    ),
    array(
        'regex'  => '/^(S)(.{9})(.{20})(.{12})/',
        'fields' => array('recordId', 'ssn', 'lastName', 'firstName',),
    ),
    array(
        'regex'  => '/^(T)(.{7})(.{4})(.{14})/',
        'fields' => array('recordId', 'totalEmployees', 'taxingEntity', 'stateQtrTotal'),
    ),
    array(
        'regex'  => '/^(F)(.{10})(.{10})(.{4})/',
        'fields' => array('recordId', 'totalEmployees', 'totalEmployers', 'taxingEntity',),
    ),
);

Затем вы можете читать файл данных по строкам и подавать его во входной фильтр:

$input = My_Filter_Input($inputFilterRules, $inputValidatorRules);
foreach (file($filename) as $line) {
    $input->setDataFromFixedWidth($line, $recordRules);
    if ($input->isValid()) {
        // do something useful
    }
    else {
        // scream and shout
    }
}

Чтобы форматировать данные для записи в файл, вы, вероятно, захотите написать собственный фильтр StringPad, который обертывает внутреннюю функцию str_pad. Затем для каждой записи в вашем наборе данных:

$output = My_Filter_Input($outputFilterRules);
foreach ($dataset as $record) {
    $output->setData($record);
    $line = implode('', $output->getEscaped()) . "\n";
    fwrite($outputFile, $line);
}

Надеюсь, это поможет!

Ответ 4

Я думаю, вам нужно немного больше информации, чем вы поставили: Какие структуры данных вы хотели бы использовать для определения записей и столбцов? Похоже, что это довольно специализированный класс, который потребует настройки для вашего конкретного случая использования.

У меня есть класс PHP, который я написал, который в основном делает то, что вы ищете, но полагаясь на другие классы, которые мы используем в нашей системе. Если вы можете предоставить типы структур данных, которые вы хотите использовать, я могу проверить, будет ли он работать для вас и отправить его.

Примечание. Я публиковал этот ответ раньше с общедоступного компьютера, и я не мог заставить его казаться от меня (он был показан как случайный пользователь). Если вы видите это, пожалуйста, проигнорируйте ответ от "john".

Ответ 5

Если это текстовый файл с разделенными полями, вам нужно будет написать его самостоятельно. Наверное, это не большая проблема. Хорошая организация, сэкономит много времени.

  • Ваша потребность в универсальном способе определения структур. То есть XML.
  • Вам нужно что-то генерировать... особенно я предпочитаю шаблон Smarty для этого.

Итак, это:

   <group>

      <entry>123</entry>

      <entry>123</entry>

      <entry>123</entry>

    </group>

Легко интерпретироваться в тесте с помощью этого шаблона:

{section name=x1 loop=level1_arr}

{--output root's--}

  {section name=x2 loop=level1_arr[x1].level2_arr}

     {--output entry's--}

  {/section}

{/section}

Это просто идея.

Но представьте себе:

  • Вам нужен xml
  • Вам нужен шаблон

то есть. 2 определения для абстрактной структуры текста

Ответ 6

Возможно, функции dbase - это то, что вы хотите использовать. Они не OOP, но, вероятно, было бы не слишком сложно построить класс, который будет действовать на функции, предоставляемые в наборе dbase.

Взгляните на приведенную ниже ссылку для получения подробной информации о функциях dbase, доступных в PHP. Если вы просто хотите создать файл для импорта в другую систему, эти функции должны работать на вас. Просто убедитесь, что вы обратите внимание на предупреждения. Некоторые из основных предупреждений:

  • Нет поддержки индексов или полей memo.
  • Поддержка блокировки отсутствует.
  • Два параллельных процесса веб-сервера, изменяющих один и тот же файл dBase, скорее всего, разрушат вашу базу данных.

http://php.net/manual/en/book.dbase.php

Ответ 7

Мне жаль, что я не могу помочь вам с прямым классом, я видел кое-что, что делает это, но я не могу вспомнить, где это так жаль, но это должно быть просто для кодера для сборки,

Итак, как я видел эту работу в примере:

php читает данные

php затем использует флаг (E.G a $_GET ['type']), чтобы знать, как выводить данные E.G Printer, HTML, Excel

Итак, вы создаете файлы шаблонов для каждой версии, а затем в зависимости от загружаемого флага и использования определенного шаблона, так как для Fixed Width это HTML-вещь, а не PHP, поэтому это должно быть сделано в шаблонах CSS

Затем из этого вы можете выводить свои данные, как когда-либо любой пользователь этого требует,

Smarty Templates неплохо подходят для этого, а затем заголовок php для отправки типа контента, когда это требуется.