Как разобрать файл OFX (Версия 1.0.2) в PHP?
У меня есть файл OFX, загруженный из Ситибанка, этот файл имеет DTD, определенный по адресу http://www.ofx.net/DownloadPage/Files/ofx102spec.zip (файл OFXBANK.DTD), файл OFX выглядит как действительный SGML. Я пытаюсь с DomDocument из PHP 5.4.13, но я получаю несколько предупреждений, и файл не анализируется. Мой код:
$file = "source/ACCT_013.OFX";
$dtd = "source/ofx102spec/OFXBANK.DTD";
$doc = new DomDocument();
$doc->loadHTMLFile($file);
$doc->schemaValidate($dtd);
$dom->validateOnParse = true;
Файл OFX начинается как:
OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<DTSERVER>20130331073401
<LANGUAGE>SPA
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>0
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<STMTRS>
<CURDEF>COP
<BANKACCTFROM> ...
Я открыт для установки и использования любой программы на сервере (Centos) для вызова из PHP.
PD: Этот класс http://www.phpclasses.org/package/5778-PHP-Parse-and-extract-financial-records-from-OFX-files.html не работает для меня.
3 ответа
Ну, во-первых, даже XML является подмножеством SGML, действительный файл SGML не должен быть правильно сформированным файлом XML. XML является более строгим и не использует все функции, которые предлагает SGML.
Как DOMDocument
основан на XML (а не на SGML), это не совсем совместимо.
Рядом с этой проблемой см. Раздел 2.2 "Открыть заголовки Financial Exchange" в Ofexfin1.doc, в котором объясняется, что
Содержимое файла Open Financial Exchange состоит из простого набора заголовков, за которыми следует содержимое, определенное этим заголовком.
и далее:
Пустая строка следует за последним заголовком. Затем (для типа OFXSGML) данные, читаемые SGML, начинаются с тега
.
Так что найдите первую пустую строку и уберите все, пока там. Затем загрузите часть SGML в DOMDocument, сначала преобразовав SGML в XML:
$source = fopen('file.ofx', 'r');
if (!$source) {
throw new Exception('Unable to open OFX file.');
}
// skip headers of OFX file
$headers = array();
$charsets = array(
1252 => 'WINDOWS-1251',
);
while(!feof($source)) {
$line = trim(fgets($source));
if ($line === '') {
break;
}
list($header, $value) = explode(':', $line, 2);
$headers[$header] = $value;
}
$buffer = '';
// dead-cheap SGML to XML conversion
// see as well http://www.hanselman.com/blog/PostprocessingAutoClosedSGMLTagsWithTheSGMLReader.aspx
while(!feof($source)) {
$line = trim(fgets($source));
if ($line === '') continue;
$line = iconv($charsets[$headers['CHARSET']], 'UTF-8', $line);
if (substr($line, -1, 1) !== '>') {
list($tag) = explode('>', $line, 2);
$line .= '</' . substr($tag, 1) . '>';
}
$buffer .= $line ."\n";
}
// use DOMDocument with non-standard recover mode
$doc = new DOMDocument();
$doc->recover = true;
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$save = libxml_use_internal_errors(true);
$doc->loadXML($buffer);
libxml_use_internal_errors($save);
echo $doc->saveXML();
Этот пример кода затем выводит следующий (переформатированный) XML, который также показывает, что DOMDocument загрузил данные правильно:
<?xml version="1.0"?>
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<DTSERVER>20130331073401</DTSERVER>
<LANGUAGE>SPA</LANGUAGE>
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>0</TRNUID>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<STMTRS><CURDEF>COP</CURDEF><BANKACCTFROM> ...</BANKACCTFROM>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>
Я не знаю, может ли это быть проверено на DTD тогда. Может быть, это работает. Кроме того, если SGML не записан со значениями тега в той же строке (и требуется только один элемент в каждой строке), то это хрупкое преобразование прервется.
Простейший OFX-анализ в массив с легким доступом ко всем значениям и транзакциям.
function parseOFX($ofx) {
$OFXArray=explode("<",$ofx);
$a=array();
foreach ($OFXArray as $v) {
$pair=explode(">",$v);
if (isset($pair[1])) {
if ($pair[1]!=NULL) {
if (isset($a[$pair[0]])) {
if (is_array($a[$pair[0]])) {
$a[$pair[0]][]=$pair[1];
} else {
$temp=$a[$pair[0]];
$a[$pair[0]]=array();
$a[$pair[0]][]=$temp;
$a[$pair[0]][]=$pair[1];
}
} else {
$a[$pair[0]]=$pair[1];
}
}
}
}
return $a;
}
Я использую это:
$source = utf8_encode(file_get_contents('a.ofx'));
//add end tag
$source = preg_replace('#^<([^>]+)>([^\r\n]+)\r?\n#mU', "<$1>$2</$1>\n", $source);
//skip header
$source = substr($source, strpos($source,'<OFX>'));
//convert to array
$xml = simplexml_load_string($source);
$array = json_decode(json_encode($xml),true);
print_r($array);