PHP -处理无效的XML

oxosxuxt  于 2023-04-19  发布在  PHP
关注(0)|答案(3)|浏览(132)

我使用SimpleXML来加载一些xml文件(这些文件不是我写的/提供的,也不能真正更改格式)。
有时候(比如每50个左右的文件中有一两个)它们不会转义任何特殊字符(大多数是&,但有时也会转义其他随机的无效字符)。这是因为SimpleXML和php会失败,我真的不知道有什么好的方法来处理解析无效的XML。
我的第一个想法是将XML作为字符串进行预处理,并将所有字段作为CDATA放入,这样它就可以工作了,但是由于一些不好的原因,我需要处理的XML将其所有数据放在属性字段中。因此我不能使用CDATA的想法。XML的一个例子是:

<Author v="By Someone & Someone" />

在用SimpleXML加载XML之前,处理此问题以替换XML中所有无效字符的最佳方法是什么?

hujrc8aj

hujrc8aj1#

你需要的是使用libxml的内部错误来定位无效字符并相应地转义它们。这里是我如何编写它的一个模型。看看libxml_get_errors()的错误信息。

function load_invalid_xml($xml)
{
    $use_internal_errors = libxml_use_internal_errors(true);
    libxml_clear_errors(true);

    $sxe = simplexml_load_string($xml);

    if ($sxe)
    {
        return $sxe;
    }

    $fixed_xml = '';
    $last_pos  = 0;

    foreach (libxml_get_errors() as $error)
    {
        // $pos is the position of the faulty character,
        // you have to compute it yourself
        $pos = compute_position($error->line, $error->column);
        $fixed_xml .= substr($xml, $last_pos, $pos - $last_pos) . htmlspecialchars($xml[$pos]);
        $last_pos = $pos + 1;
    }
    $fixed_xml .= substr($xml, $last_pos);

    libxml_use_internal_errors($use_internal_errors);

    return simplexml_load_string($fixed_xml);
}
4sup72z8

4sup72z82#

我认为创建compute_position函数的工作方法是在处理之前使xml字符串变平。重写Josh发布的代码:

function load_invalid_xml($xml)
{
    $use_internal_errors = libxml_use_internal_errors(true);
    libxml_clear_errors(true);

    $sxe = simplexml_load_string($xml);

    if ($sxe)
    {
        return $sxe;
    }

    $fixed_xml = '';
    $last_pos  = 0;

    // make string flat
    $xml = str_replace(array("\r\n", "\r", "\n"), "", $xml);

    // get file encoding
    $encoding = mb_detect_encoding($xml);

    foreach (libxml_get_errors() as $error)
    {
        $pos = $error->column;
        $invalid_char = mb_substr($xml, $pos, 1, $encoding);
        $fixed_xml .= substr($xml, $last_pos, $pos - $last_pos) . htmlspecialchars($invalid_char);
        $last_pos = $pos + 1;
    }
    $fixed_xml .= substr($xml, $last_pos);

    libxml_use_internal_errors($use_internal_errors);

    return simplexml_load_string($fixed_xml);
}

我已经添加了编码的东西,因为我有问题,简单的数组[索引]的方式获得字符串的字符。
这一切都应该工作,但不知道为什么,我已经看到$error-〉列给我一个不同的数字比它应该.试图调试这个简单地添加一些无效的字符在xml中,并检查它会返回什么值,但没有运气.希望有人能告诉我这种方法有什么问题.

06odsfpq

06odsfpq3#

尽管这个问题已经存在了10年(当我输入这个问题时),我仍然遇到类似的XML解析问题(PHP8.1),这就是为什么我在这里结束.已经给出的答案是有帮助的,但要么不完整,不一致或不适合我的问题,我怀疑原始海报也是如此.
检查内部XML解析问题似乎是正确的,但是有735个错误代码(参见https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-xmlerror.html),因此更适合的解决方案似乎是合适的。
我在上面使用了“不一致”这个词,因为其他答案中最好的(@Adam Szmyd)混合了多字节字符串处理和非多字节字符串处理。
下面的代码使用Adam的代码作为基础,我根据我的情况重新编写了它,我觉得可以根据实际遇到的问题进一步扩展。
这段代码的本质是它将“每一个”(在我的实现中,只有1个)XML解析错误作为单独的情况处理。我遇到的错误是一个无法识别的HTML实体(ç- ç),所以我使用PHP实体替换。

function load_invalid_xml($xml)
{
    $use_internal_errors = libxml_use_internal_errors(true);
    libxml_clear_errors(true);

    $sxe = simplexml_load_string($xml);

    if ($sxe)
        return $sxe;

    $fixed_xml = '';
    $last_pos  = 0;

    // make string flat
    $xmlFlat = mb_ereg_replace( '(\r\n|\r|\n)', '', $xml );

    // Regenerate the error but using the flattened source so error offsets are directly relevant
    libxml_clear_errors();
    $xml_doc = @simplexml_load_string( $xmlFlat );

    foreach (libxml_get_errors() as $error)
    {
        $pos = $error->column - 1; // ->column appears to be 1 based, not 0 based

        switch( $error->code ) {

            case 26: // error undeclared entity
            case 27: // warning undeclared entity
                if ($pos >= 0) { // the PHP docs suggest this not always set (in which case ->column is == 0)

                    $left = mb_substr( $xmlFlat, 0, $pos );
                    $amp = mb_strrpos( $left, '&' );

                    if ($amp !== false) {

                        $entity = mb_substr( $left, $amp );
                        $fixed_xml .= mb_substr( $xmlFlat, $last_pos, $amp - $last_pos )
                            . html_entity_decode( $entity );
                        $last_pos = $pos;
                    }
                }
                break;

            default:
        }
    }
    $fixed_xml .= mb_substr($xml, $last_pos);

    libxml_use_internal_errors($use_internal_errors);

    return simplexml_load_string($fixed_xml);
}

相关问题