Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
namespace Hamcrest\Xml;
3
 
4
/*
5
 Copyright (c) 2009 hamcrest.org
6
 */
7
use Hamcrest\Core\IsEqual;
8
use Hamcrest\Description;
9
use Hamcrest\DiagnosingMatcher;
10
use Hamcrest\Matcher;
11
 
12
/**
13
 * Matches if XPath applied to XML/HTML/XHTML document either
14
 * evaluates to result matching the matcher or returns at least
15
 * one node, matching the matcher if present.
16
 */
17
class HasXPath extends DiagnosingMatcher
18
{
19
 
20
    /**
21
     * XPath to apply to the DOM.
22
     *
23
     * @var string
24
     */
25
    private $_xpath;
26
 
27
    /**
28
     * Optional matcher to apply to the XPath expression result
29
     * or the content of the returned nodes.
30
     *
31
     * @var Matcher
32
     */
33
    private $_matcher;
34
 
35
    public function __construct($xpath, Matcher $matcher = null)
36
    {
37
        $this->_xpath = $xpath;
38
        $this->_matcher = $matcher;
39
    }
40
 
41
    /**
42
     * Matches if the XPath matches against the DOM node and the matcher.
43
     *
44
     * @param string|\DOMNode $actual
45
     * @param Description $mismatchDescription
46
     * @return bool
47
     */
48
    protected function matchesWithDiagnosticDescription($actual, Description $mismatchDescription)
49
    {
50
        if (is_string($actual)) {
51
            $actual = $this->createDocument($actual);
52
        } elseif (!$actual instanceof \DOMNode) {
53
            $mismatchDescription->appendText('was ')->appendValue($actual);
54
 
55
            return false;
56
        }
57
        $result = $this->evaluate($actual);
58
        if ($result instanceof \DOMNodeList) {
59
            return $this->matchesContent($result, $mismatchDescription);
60
        } else {
61
            return $this->matchesExpression($result, $mismatchDescription);
62
        }
63
    }
64
 
65
    /**
66
     * Creates and returns a <code>DOMDocument</code> from the given
67
     * XML or HTML string.
68
     *
69
     * @param string $text
70
     * @return \DOMDocument built from <code>$text</code>
71
     * @throws \InvalidArgumentException if the document is not valid
72
     */
73
    protected function createDocument($text)
74
    {
75
        $document = new \DOMDocument();
76
        if (preg_match('/^\s*<\?xml/', $text)) {
77
            if (!@$document->loadXML($text)) {
78
                throw new \InvalidArgumentException('Must pass a valid XML document');
79
            }
80
        } else {
81
            if (!@$document->loadHTML($text)) {
82
                throw new \InvalidArgumentException('Must pass a valid HTML or XHTML document');
83
            }
84
        }
85
 
86
        return $document;
87
    }
88
 
89
    /**
90
     * Applies the configured XPath to the DOM node and returns either
91
     * the result if it's an expression or the node list if it's a query.
92
     *
93
     * @param \DOMNode $node context from which to issue query
94
     * @return mixed result of expression or DOMNodeList from query
95
     */
96
    protected function evaluate(\DOMNode $node)
97
    {
98
        if ($node instanceof \DOMDocument) {
99
            $xpathDocument = new \DOMXPath($node);
100
 
101
            return $xpathDocument->evaluate($this->_xpath);
102
        } else {
103
            $xpathDocument = new \DOMXPath($node->ownerDocument);
104
 
105
            return $xpathDocument->evaluate($this->_xpath, $node);
106
        }
107
    }
108
 
109
    /**
110
     * Matches if the list of nodes is not empty and the content of at least
111
     * one node matches the configured matcher, if supplied.
112
     *
113
     * @param \DOMNodeList $nodes selected by the XPath query
114
     * @param Description $mismatchDescription
115
     * @return bool
116
     */
117
    protected function matchesContent(\DOMNodeList $nodes, Description $mismatchDescription)
118
    {
119
        if ($nodes->length == 0) {
120
            $mismatchDescription->appendText('XPath returned no results');
121
        } elseif ($this->_matcher === null) {
122
            return true;
123
        } else {
124
            foreach ($nodes as $node) {
125
                if ($this->_matcher->matches($node->textContent)) {
126
                    return true;
127
                }
128
            }
129
            $content = array();
130
            foreach ($nodes as $node) {
131
                $content[] = $node->textContent;
132
            }
133
            $mismatchDescription->appendText('XPath returned ')
134
                                                    ->appendValue($content);
135
        }
136
 
137
        return false;
138
    }
139
 
140
    /**
141
     * Matches if the result of the XPath expression matches the configured
142
     * matcher or evaluates to <code>true</code> if there is none.
143
     *
144
     * @param mixed $result result of the XPath expression
145
     * @param Description $mismatchDescription
146
     * @return bool
147
     */
148
    protected function matchesExpression($result, Description $mismatchDescription)
149
    {
150
        if ($this->_matcher === null) {
151
            if ($result) {
152
                return true;
153
            }
154
            $mismatchDescription->appendText('XPath expression result was ')
155
                                                    ->appendValue($result);
156
        } else {
157
            if ($this->_matcher->matches($result)) {
158
                return true;
159
            }
160
            $mismatchDescription->appendText('XPath expression result ');
161
            $this->_matcher->describeMismatch($result, $mismatchDescription);
162
        }
163
 
164
        return false;
165
    }
166
 
167
    public function describeTo(Description $description)
168
    {
169
        $description->appendText('XML or HTML document with XPath "')
170
                                ->appendText($this->_xpath)
171
                                ->appendText('"');
172
        if ($this->_matcher !== null) {
173
            $description->appendText(' ');
174
            $this->_matcher->describeTo($description);
175
        }
176
    }
177
 
178
    /**
179
     * Wraps <code>$matcher</code> with {@link Hamcrest\Core\IsEqual)
180
     * if it's not a matcher and the XPath in <code>count()</code>
181
     * if it's an integer.
182
     *
183
     * @factory
184
     */
185
    public static function hasXPath($xpath, $matcher = null)
186
    {
187
        if ($matcher === null || $matcher instanceof Matcher) {
188
            return new self($xpath, $matcher);
189
        } elseif (is_int($matcher) && strpos($xpath, 'count(') !== 0) {
190
            $xpath = 'count(' . $xpath . ')';
191
        }
192
 
193
        return new self($xpath, IsEqual::equalTo($matcher));
194
    }
195
}