PHP-библиотека для создания / работы с текстовыми файлами фиксированной ширины - PullRequest
18 голосов
/ 26 апреля 2011

У нас есть веб-приложение, которое отслеживает время, зарплату и работу с персоналом.В результате мы должны написать много файлов данных фиксированной ширины для экспорта в другие системы (налоговые декларации, 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)
         )
     );

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

Ответы [ 7 ]

8 голосов
/ 27 мая 2011

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

(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's 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 можно расширить, чтобы разрешить чтение, но это может выходить за рамки этого ответа.

3 голосов
/ 31 мая 2011

Я с удовольствием использовал этот класс для аналогичного использования ранее.Это файл php-classes, но он очень хорошо оценен и проверен многими.Он не новый (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 цента, удачи!

1 голос
/ 27 мая 2011

Я не знаю ни одной библиотеки 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);
}

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

0 голосов
/ 26 мая 2011

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

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

PHP читает в данных

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

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

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

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

0 голосов
/ 07 мая 2011

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

  1. Вам нужен универсальный способ определения конструкций. То есть XML.
  2. Вам нужно что-то сгенерировать ... особенно я предпочитаю шаблонизацию 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}

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

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

  1. Вам нужен xml
  2. Вам нужен шаблон

т.е. 2 определения для выделения любого текста структуры

0 голосов
/ 04 мая 2011

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

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

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

0 голосов
/ 28 апреля 2011

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

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

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

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

...