PHP用html内容解析xml

时间:2011-12-06 12:43:52

标签: php xml parsing

是否可以在php中使用默认的xml类来解析xml文件,使得只有一个命名空间中的元素被认为是xml?我想解析一些元素包含html代码的xml文件,最好不要用cdata标签封装每个元素,或者转义所有特殊字符。由于html的语法与xml非常相似,因此大多数解析器都无法正确解析它。

示例:

<ns:root>
    <ns:date>
        06-12-2011
    </ns:date>
    <ns:content>
        <html>
        <head>
        <title>Sometitle</title>
        </head>
        <body>
        --a lot of stuff here
        </body>
        </html>
    </ns:content>
</ns:root>

在这个例子中,我希望里面的所有html内容都是该元素的内容,不应该自己解析它。这可以使用像simplexml等默认解析器,还是应该编写自己的解析器?

编辑:让我更好地解释一下我的情况: 我想创建一个小的个人php框架,其中代码与HTML分开(类似于MVC,但不完全相同)。但是,许多HTML代码在多个页面中是相同的,但不是所有内容,以及来自例如的一些数据。应该在某些页面中插入数据库,与普通网站没有什么不同。所以我提出了使用单独的html组件文件的想法,这些文件可以通过html脚本进行解析。这看起来像这样:

main.fw:

<html>
<head>
    <title>
        <fw:placeholder name="title" />
    </title>
</head>
<body>
    <div id="menubar">
        <ul>
            <li>page1</li>
            <li>page2</li>
        </ul>
    </div>
    <div id="content>
        <fw:placeholder name="maincontent" />
    </div>
</body>
</html>

page1.fw

<fw:component file="main.fw">
    <fw:content name="title">
        page1
    </fw:content>
    <fw:content name="maincontent" />
        some content with html
    </fw:content>
</fw:component>

解析后的结果:                                第1页                                         

                    
  • 第1页
  •                 
  • 第2页
  •             
                              一些内容与HTML                   

这个问题主要是关于第二种类型的文件,其中html嵌套在xml元素中。

4 个答案:

答案 0 :(得分:1)

使用DOMDocument时可以使用textContent:http://www.php.net/manual/en/class.domnode.php

答案 1 :(得分:1)

您希望HTML代码被视为非XML代码,而且确切地说是设计了什么字符数据(CDATA)。

<ns:root>
    <ns:date>
        06-12-2011
    </ns:date>
    <ns:content>
        <![CDATA[
            <html>
            <head>
            <title>Sometitle</title>
            </head>
            <body>
            --a lot of stuff here
            </body>
            </html>
        ]]>
    </ns:content>
</ns:root>

比编写自己的解析器更好地依赖于此。使用XMLWriter::writeCData()方法编写CDATA部分。

重要提示: CDATA部分内的HTML标记无需编码!

引自Wikipedia CDATA

但是,如果这样写:

<![CDATA[<sender>John Smith</sender>]]>

然后代码的解释方式与它的编写方式相同:

&lt;sender&gt;John Smith&lt;/sender&gt;

答案 2 :(得分:1)

包含非XML部分的XML文件不是XML文件。因此,您不能指望XML解析器能够解析它。对于XML文档,整个过程必须是XML。

你要求的基本上是“是否有解析器可以解析我的角度括号语言。”也许DOMDocument->loadHTML()html5lib会根据您的期望来解释它,但不能保证。

对于包含的“html”位来说,有效的XML是否真的是一个可怕的负担?无论如何,这都是良好的HTML卫生,如果您愿意这样做,您可以非常轻松地使用XSL模板实现整个视图系统。节点感知模板系统的大多数好处恰恰在于您可以直接操作节点并且可以很好地保证最终文档的有效性。为什么节点意识的负担没有任何好处?您也可以像其他模板系统一样使用基于字符串的系统。至少它会更快。

请注意,一旦构建了最终的DOM,就可以将其输出为其他内容,例如HTML,因为所有输入模板都是XML并不意味着您的输出必须是。

答案 3 :(得分:1)

我决定创建一个简单的解析器来查看结果。由于我不解析有效的XML,从现在开始我将其称为XMLIsh。

解析器实际上工作得很好,并且性能也不差:我做了一些测试,我发现它只比有效xml文档上的SimpleXMLElement慢10倍,而SimpleXMLElement是用PHP功能构建的我的功能只是php。此解析器也适用于'XMLIsh'文档,如前所述。因此,只要不需要超高速,这可能是一个有效的解决方案。

在我的情况下,这些文档只会被解析一次,因为输出是缓存的,所以我认为这对我有用。

无论如何,这是我的代码:

/**
 * This function parses a string as an XMLIsh document. An XMLIsh document is very similar to xml, but only one namespace should be parsed. 
 * 
 * parseXMLish walks through the document and creates a tree while doing so. 
 * Each element will be represented as an array, with the following content:
 * -index = 0: An array with as first element (index = 0) the type of the element. All following elements are its arguments with index=name and value=value.
 * -index = 1: Optional:an array with the content of this element. If the content is a string, this array will only have one element, namely the content of the string.
 * 
 * @param &$string The XMLIsh string to be parsed
 * @param $namespace The namespace which should be parsed.
 * @param &$offset The starting point of parsing. Default = 0
 * @param $previousTag The current opening tag. This argument shouldn't be set manually, this argument is needed for this function to check if a closing tag is valid.
 */
function parseXMLish(&$string,$namespace,&$offset=0,$openingTag = ""){
    //Whitespace doesn't matter, so trim it:)
    $string = trim($string);
    $result = array();
    //We need to find our mvc elements. These elements use xml syntax and should have the namespace mvc. 
    //Opening, closing and self closing tags are found.
    while(preg_match("/<(\/)?{$namespace}:(\w*)(.*?)(\/)?>/",$string,$matches,PREG_OFFSET_CAPTURE,$offset)){
        //Before our first mvc element, other text might have been found (e.g. html code). 
        //This should be added to our result array first. Again, strip the whitespace.
        $preText = substr($string,$offset,$matches[0][1]-$offset);
        $trimmedPreText = trim($preText);
        if (!empty($trimmedPreText))
            $result[] = $trimmedPreText;
        //We could have find 2 types of tags: closing and opening (including self closing) tags.
        //We need to distinguish between those two.
        if ($matches[1][0] == ''){
            //This tag was an opening tag. This means we should add this to the result array.
            //We add the name of this tag to the element first.
            $result[][0][0] = $matches[2][0];
            //Tags can also have arguments. We will find them here, and store them in the result array.
            preg_match_all("/\s*(\w+=[\"']?\S+[\"'])/",$matches[0][0],$arguments);
            foreach($arguments[1] as $argument){
                list($name,$value)=explode("=",$argument);
                $value = str_replace("\"","",$value);
                $value = str_replace("'","",$value);
                $result[count($result)-1][0][$name]=$value;
            }
            //We need to recalculate our offset. So lets do that. 
            $offset +=  strlen($preText) + strlen($matches[0][0]);
            //Now we will have to fill our element with content. 
            //This is only necessary if this is a regular opening tag, and not a self-closing tag.
            if (!(isset($matches[4]) && $matches[4][0] == "/")){
                $content = parseXMLish($string, $namespace, $offset,$matches[2][0]);                
            }
            //Only add content when there is any. 
            if (!empty($content))
                $result[count($result)-1][] = $content;
        }else{
            //This tag is a closing tag. It means that we only have to update the offset, and that we can go one level up
            //That is: return what we have so far back to the previous level. 
            //Note: the closing tag is the closing tag of the previous level, not of the current level. 
            if ($matches[2][0] != $openingTag)
                throw new Exception("Closing tag doesn't match the opening tag. Opening tag: $previousTag. Closing tag: {$matches[2][0]}");
            $offset +=  strlen($preText) + strlen($matches[0][0]);
            return $result;
        }
    }
    //If we have any text left after our last element, we should add that to the array too.
    $postText = substr($string,$offset);
    if (!empty($postText))
        $result[] = $postText;

    //We're done!
    return $result;     
}