在2个标记之间查找格式错误的XML并包含嵌套标记

时间:2014-05-21 22:43:52

标签: php xml parsing xml-parsing html-parsing

是否有任何简单的方法可以在格式错误的XML的2个标签之间查找文本并忽略嵌套?

鉴于此内容:

<div>
    Some content 1
    </
    <some:tag>
        Section 1
    </some:tag>
    <b>Some content 2
    <some:tag>
        Section 2
        <some:tag>
            Section 3
        </some:tag>
    </some:tag>
    Some content 3
    </p>
</div>

注意:它故意格式错误。我不能/不想使用正确的HTML / XML解析器,因为我的内容没有正确形成,或者在某些情况下甚至不是XML。同样,我不能/不想对它进行整洁,因为它并不总是HTML / XML。

所以我需要找到<some:tag></some:tag>之间的文字,包括嵌套代码。

上述内容将导致:

array (size=2)

  0 => string '<some:tag>
            Section 1
        </some:tag>' (length=52)

  1 => string '<some:tag>
            Section 2
            <some:tag>
                Section 3
            </some:tag>
        </some:tag>' (length=125)

您尝试过的强制性事项:

我已尝试使用strpos / substr来取出匹配项,但我在逻辑上有点迷失:

function findSomeTag($str) {
    $result = [];
    $startTag = "<some:tag>";
    $endTag = "</some:tag>";
    $offset = 0;
    $start = strpos($str, $startTag, $offset);
    while ($start !== false) {
        $nextStart = strpos($str, $startTag, $start + 1);
        $nextEnd = strpos($str, $endTag, $start + 1);
        if ($nextStart === false || $nextEnd < $nextStart) {
            $result[] = substr($str, $start, $nextEnd - $start + strlen($endTag));
        }
        $start = $nextStart;
    }
    return $result;
}

(注意:以上功能完全不起作用,可能会无限循环。)

4 个答案:

答案 0 :(得分:1)

要包含嵌套代码,您可以计算当前打开的代码的数量。

同时$nextEnd > $nextStart增加$counter,并且仅在您$nextEnd < $nextStart && $counter == 1时添加新结果(您有一个开放标记)。如果$nextEnd < $nextStart && $counter < 1递减$counter

答案 1 :(得分:1)

与我的其他答案不同,此版本将读取带有嵌套标签的标签:

$text = "
<div>
    Some content 1
    </
    <some:tag>
        Section 1
    </some:tag>
    <b>Some content 2
    <some:tag>
        Section 2
        <some:tag>
            Section 3
        </some:tag>
    </some:tag>
    Some content 3
    </p>
</div>
";

$parser = new Parser( new TextReader($text) );
$found = $parser->findTags("<some:tag>", "</some:tag>");

class TextReader {
    private $idx = 0;
    private $reading;
    private $lastIdx;

    public function __construct($reading) {
        $this->reading = $reading;
        $this->lastIdx = strlen($reading) - 1;
    }

    public function hasMore() {
        return $this->idx < $this->lastIdx;
    }

    public function nextChar() {
        if( !$this->hasMore() ) return null;

        return $this->reading[$this->idx++];
    }

    public function rewind($howFar) {
        $this->idx -= $howFar;
        if( $this->idx < 0 ) $this->idx = 0;
    }
}


class Parser {
    private $TextReader;

    public function __construct($TextReader) {
        $this->TextReader = $TextReader;
    }

    public function findTags($startTagName, $endTagName) {
        $found = array();

        while( ($next = $this->findNextTag($startTagName, $endTagName)) != null ) {
            $found[] = $next;
        }

        return $found;
    }

    public function findNextTag($startTagName, $endTagName) {
        // find the start of our first tag
        $junk = $this->readForTag($startTagName);
        if( $junk == null ) return null; // didn't find another tag

        $nests = 0;
        $started = false;

        $startLength = strlen($startTagName);
        $endLength = strlen($endTagName);

        $readSoFar = "";

        while($this->TextReader->hasMore()) {
            // found a start tag
            if( substr( $readSoFar, $readSoFarLength - $startLength ) == $startTagName ) {
                $started = true;
                $nests++;
            }

            // found an end tag
            if( substr( $readSoFar, $readSoFarLength - $endLength ) == $endTagName ) $nests--;

            $readSoFar .= $this->TextReader->nextChar();

            // if we've started, and we found as many starts as ends
            if( $started && $nests == 0 ) return $readSoFar;
        }

        return null;
    }

    /*
     * read the Text Reader until you find a certain tag, and
     * return what you read before finding the tag, including the tag itself
     *
     * Text Reader will be rewound to the beginning of the tag
     */
    private function readForTag($tagName) {
        $readSoFar = "";

        $tagNameLength = strlen($tagName);

        while($this->TextReader->hasMore()) {
            // if the last few characters read are the tag
            if( substr( $readSoFar, strlen($readSoFar) - $tagNameLength ) == $tagName ) {
                // rewind
                $this->TextReader->rewind($tagNameLength);

                // return what we've read
                return $readSoFar;
            }

            $readSoFar .= $this->TextReader->nextChar();
        }

        return null;
    }
}

答案 2 :(得分:0)

我认为进行任何解析的最简单方法是使用类似状态机的东西。基本上,您可以定义一组状态以及离开这些状态并进入其他状态的条件。

假设你的文本在某种文本阅读器中可以给你下一个字符并向前移动指针,并且还可以将指针倒回一定数量的字符。

然后你可以创建一个像这样的状态机(它原来是一个简单的状态机,只有一个状态基本上在自身循环):

class StateMachine {
    private $TextReader;

    public function __construct($TextReader) {
        $this->TextReader = $TextReader;
    }

    public function getTagContents($startTagName, $endTagName) {
        $tagsFound = array();

        // read until we get to the start of a tag
        while( $this->stateReadForTag($startTagName) != null ) {
            // now read until we find the end
            $contents = $this->stateReadForTag($endTagName);

            // didn't find the end
            if( $contents == null ) break;

            $tagsFound[] = $contents;
        }

        return $tagsFound;
    }

    /*
     * read the Text Reader until you find a certain tag, and
     * return what you read before finding the tag, including the tag itself
     *
     * Text Reader will be rewound to the beginning of the tag
     */
    private function stateReadForTag($tagName) {
        $readSoFar = "";

        $tagNameLength = strlen($tagName);

        while($this->TextReader->hasMore()) {
            // if the last few characters read are the tag
            if( substr( $readSoFar, strlen($readSoFar) - $tagNameLength ) == $tagName ) {
                // rewind
                $this->TextReader->rewind($tagNameLength);

                // return what we've read
                return $readSoFar;
            }

            $readSoFar .= $this->TextReader->nextChar();
        }

        return null;
    }
}

然后这样称呼:

$found = $myStateMachine->getTagContents("<some:tag>", "</some:tag>");

TextReader可能如下所示:

class TextReader {
    private $idx = 0;
    private $reading;
    private $lastIdx;

    public function __construct($reading) {
        $this->reading = $reading;
        $this->lastIdx = strlen($reading) - 1;
    }

    public function hasMore() {
        return $this->idx < $this->lastIdx;
    }

    public function nextChar() {
        if( !$this->hasMore() ) return null;

        return $this->reading[$this->idx++];
    }

    public function rewind($howFar) {
        $this->idx -= $howFar;
        if( $this->idx < 0 ) $this->idx = 0;
    }
}

然后你可以这样调用状态机:

$myStateMachine = new StateMachine( new TextReader($myXmlFileContents) );
$found = $myStateMachine->getTagContents("<some:tag>", "</some:tag>");

答案 3 :(得分:0)

结束了这个:

class TagExtractor {

    public $content;
    public $tag;

    public function getTagContent() {
        $result = [];
        $startTag = "<{$this->getTag()}>";
        $endTag = "</{$this->getTag()}>";
        $content = $this->getContent();
        $offset = strpos($content, $startTag);
        while ($offset !== false) {
            $end = $this->findEnd($content, $offset, $startTag, $endTag);
            $result[] = substr($content, $offset, $end - $offset);
            $offset = strpos($content, $startTag, $end);
        }
        return $result;
    }

    public function findEnd($content, $offset, $startTag, $endTag, $counter = 1) {
        $offset++;
        $nextStart = strpos($content, $startTag, $offset);
        $nextEnd = strpos($content, $endTag, $offset);
        if ($nextEnd === false) {
            $counter = 0;
        } elseif ($nextStart < $nextEnd && $nextStart !== false) {
            $counter++;
            $offset = $nextStart;
        } elseif ($nextEnd < $nextStart || ($nextStart === false && $nextEnd !== false)) {
            $counter--;
            $offset = $nextEnd;
        }
        if ($counter === 0) {
            return $offset + strlen($endTag);
        }
        return $this->findEnd($content, $offset, $startTag, $endTag, $counter);
    }

    // <editor-fold defaultstate="collapsed" desc="Getters and setters">
    public function getContent() {
        return $this->content;
    }

    public function setContent($content) {
        $this->content = $content;
        return $this;
    }

    public function getTag() {
        return $this->tag;
    }

    public function setTag($tag) {
        $this->tag = $tag;
        return $this;
    }
    // </editor-fold>
}