首先,即使 XML 是 SGML 的子集,有效的 SGML 文件也一定不是格式良好的 XML 文件。 XML 更加严格,并且不使用 SGML 提供的所有功能。
As DOMDocument
是基于 XML(而不是 SGML)的,这并不真正兼容。
在该问题旁边,请参阅2.2 开放金融交易头在 Ofexfin1.doc 中它向您解释了
Open Financial Exchange 文件的内容由一组简单的标头组成,后跟该标头定义的内容
并进一步:
最后一个标题后面有一个空行。然后(对于 OFXSGML 类型),SGML 可读数据以 标记开始。
因此,找到第一个空白行并删除所有内容,直到那里。然后通过首先将 SGML 转换为 XML 将 SGML 部分加载到 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 不是使用同一行上的标记值编写的(并且每行上只需要一个元素),那么这种脆弱的转换将会中断。