Библиотека 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)
)
);
РЕДАКТИРОВАТЬ: Я не ищу совета о том, как писать такую библиотеку. Я просто хотел знать, существует ли она уже. Спасибо!
Ответы
Ответ 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 для отправки типа контента, когда это требуется.