如何在PHP中解析OFX(版本1.0.2)文件?

8

我从花旗银行下载了一个OFX文件,该文件在http://www.ofx.net/DownloadPage/Files/ofx102spec.zip(OFXBANK.DTD文件)中定义了一个DTD,OFX文件似乎是SGML有效的。 我正在尝试使用PHP 5.4.13的DomDocument,但我收到了多个警告并且文件未解析。我的代码如下:

$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调用。
PS:这个类http://www.phpclasses.org/package/5778-PHP-Parse-and-extract-financial-records-from-OFX-files.html对我没起作用。
3个回答

5
首先,即使XML是SGML的子集,有效的SGML文件不一定是格式正确的XML文件。XML更加严格,不使用SGML提供的所有特性。
由于DOMDocument基于XML而非SGML,因此两者并不兼容。
除此之外,请参阅Ofexfin1.doc中的2.2 Open Financial Exchange Headers,它解释了以下内容:
开放金融交换文件的内容包括一组简单的标头,后跟由该标头定义的内容。
然后再看一下:
最后一个标头后面跟着一个空行。然后(对于OFXSGML类型),可读取的SGML数据以<OFX>标记开头。
因此,请找到第一个空行并剥离其中的所有内容。然后将SGML部分转换为XML,加载到DOMDocument中:
$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没有使用与同一行标记的值相同的值(并且每行仅需要一个元素),那么这种脆弱的转换将会失败。


谢谢,它已经可以工作了。它已经被转换成了PHP数组,使用的是http://www.bin-co.com/php/scripts/xml2array/。 - Jose Nobile
格式看起来有点平淡。你可能想使用这个变体:http://stackoverflow.com/a/15729905/367456 - 这是一行代码。 - hakre

3

将最简单的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;
}

1
我使用这个:
$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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接