diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php
index ccd752f..e7d0aa6 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestCheckboxesZeroForm.php
@@ -56,7 +56,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $json = T
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
- if ($form_state->has('json')) {
+ if ($form_state->get('json')) {
$form_state->setResponse(new JsonResponse($form_state->getValues()));
}
else {
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 24a0ebf..50fc7c3 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -147,7 +147,7 @@ public function label() {
$label = call_user_func($label_callback, $this);
}
elseif (($label_key = $entity_type->getKey('label')) && isset($this->{$label_key})) {
- $label = $this->{$label_key};
+ $label = (string) $this->{$label_key};
}
return $label;
}
diff --git a/core/modules/views/src/Tests/Plugin/StyleOpmlTest.php b/core/modules/views/src/Tests/Plugin/StyleOpmlTest.php
index 6580c0c..df008c5 100644
--- a/core/modules/views/src/Tests/Plugin/StyleOpmlTest.php
+++ b/core/modules/views/src/Tests/Plugin/StyleOpmlTest.php
@@ -60,7 +60,7 @@ public function testOpmlOutput() {
$outline = $this->xpath('//outline[1]');
$this->assertEqual($outline[0]['type'], 'rss', 'The correct type attribute is used for rss OPML.');
$this->assertEqual($outline[0]['text'], $feed->label(), 'The correct text attribute is used for rss OPML.');
- $this->assertEqual($outline[0]['xmlurl'], $feed->getUrl(), 'The correct xmlUrl attribute is used for rss OPML.');
+ $this->assertEqual($outline[0]['xmlUrl'], $feed->getUrl(), 'The correct xmlUrl attribute is used for rss OPML.');
$view = $this->container->get('entity.manager')
->getStorage('view')
diff --git a/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php b/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php
index 6e42fa8..5189573 100644
--- a/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php
+++ b/core/modules/aggregator/src/Tests/AggregatorRenderingTest.php
@@ -129,7 +129,7 @@ public function testFeedPage() {
$outline = $this->xpath('//outline[1]');
$this->assertEqual($outline[0]['type'], 'rss', 'The correct type attribute is used for rss OPML.');
$this->assertEqual($outline[0]['text'], $feed->label(), 'The correct text attribute is used for rss OPML.');
- $this->assertEqual($outline[0]['xmlurl'], $feed->getUrl(), 'The correct xmlUrl attribute is used for rss OPML.');
+ $this->assertEqual($outline[0]['xmlUrl'], $feed->getUrl(), 'The correct xmlUrl attribute is used for rss OPML.');
// Check for the presence of a pager.
$this->drupalGet('aggregator/sources/' . $feed->id());
diff --git a/core/modules/comment/src/Tests/Views/RowRssTest.php b/core/modules/comment/src/Tests/Views/RowRssTest.php
index 89b9c1b..4536652 100644
--- a/core/modules/comment/src/Tests/Views/RowRssTest.php
+++ b/core/modules/comment/src/Tests/Views/RowRssTest.php
@@ -31,7 +31,7 @@ public function testRssRow() {
$result = $this->xpath('//item');
$this->assertEqual(count($result), 1, 'Just one comment was found in the rss output.');
- $this->assertEqual($result[0]->pubdate, gmdate('r', $this->comment->getCreatedTime()), 'The right pubDate appears in the rss output.');
+ $this->assertEqual($result[0]->pubDate, gmdate('r', $this->comment->getCreatedTime()), 'The right pubDate appears in the rss output.');
}
}
diff --git a/core/modules/editor/editor.admin.inc b/core/modules/editor/editor.admin.inc
index 83bc2cf..67ebedf 100644
--- a/core/modules/editor/editor.admin.inc
+++ b/core/modules/editor/editor.admin.inc
@@ -92,8 +92,8 @@ function editor_image_upload_settings_form(Editor $editor) {
$form['max_dimensions'] = array(
'#type' => 'item',
'#title' => t('Maximum dimensions'),
- '#field_prefix' => '
',
- '#field_suffix' => '
',
+ // '#field_prefix' => '',
+ // '#field_suffix' => '
',
'#description' => t('Images larger than these dimensions will be scaled down.'),
'#states' => $show_if_image_uploads_enabled,
);
diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
index 4e49b81..0e5ee51 100644
--- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
+++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
@@ -206,8 +206,8 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
'#title' => t('Maximum image resolution'),
'#element_validate' => array(array(get_class($this), 'validateResolution')),
'#weight' => 4.1,
- '#field_prefix' => '',
- '#field_suffix' => '
',
+ // '#field_prefix' => '',
+ // '#field_suffix' => '
',
'#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of EXIF data in the image.', array('@url' => 'http://en.wikipedia.org/wiki/Exchangeable_image_file_format')),
);
$element['max_resolution']['x'] = array(
@@ -233,8 +233,8 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
'#title' => t('Minimum image resolution'),
'#element_validate' => array(array(get_class($this), 'validateResolution')),
'#weight' => 4.2,
- '#field_prefix' => '',
- '#field_suffix' => '
',
+ // '#field_prefix' => '',
+ // '#field_suffix' => '
',
'#description' => t('The minimum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'),
);
$element['min_resolution']['x'] = array(
diff --git a/core/modules/config/src/Tests/ConfigEntityListTest.php b/core/modules/config/src/Tests/ConfigEntityListTest.php
index c578ff2..8f90ff9 100644
--- a/core/modules/config/src/Tests/ConfigEntityListTest.php
+++ b/core/modules/config/src/Tests/ConfigEntityListTest.php
@@ -174,7 +174,7 @@ function testListUI() {
// operations list.
$this->assertIdentical((string) $elements[0], 'Default');
$this->assertIdentical((string) $elements[1], 'dotted.default');
- $this->assertTrue($elements[2]->children()->xpath('//ul'), 'Operations list found.');
+ $this->assertTrue($elements[2]->children()->xpath($this->localizeXpath('//ul')), 'Operations list found.');
// Add a new entity using the operations link.
$this->assertLink('Add test configuration');
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index b137d19..010c296 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -652,9 +652,9 @@ function testDrupalRenderChildrenPostRenderCache() {
$dom = Html::load($cached_element['#markup']);
$xpath = new \DOMXPath($dom);
- $parent = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]')->length;
- $child = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length;
- $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length;
+ $parent = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]'))->length;
+ $child = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]'))->length;
+ $subchild = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper" and text()="Subchild"]'))->length;
$this->assertTrue($parent && $child && $subchild, 'The correct data is cached: the stored #markup is not affected by #post_render_cache callbacks.');
// Remove markup because it's compared above in the xpath.
@@ -719,9 +719,9 @@ function testDrupalRenderChildrenPostRenderCache() {
$dom = Html::load($cached_parent_element['#markup']);
$xpath = new \DOMXPath($dom);
- $parent = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]')->length;
- $child = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length;
- $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length;
+ $parent = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]'))->length;
+ $child = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]'))->length;
+ $subchild = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper" and text()="Subchild"]'))->length;
$this->assertTrue($parent && $child && $subchild, 'The correct data is cached for the parent: the stored #markup is not affected by #post_render_cache callbacks.');
// Remove markup because it's compared above in the xpath.
@@ -745,8 +745,8 @@ function testDrupalRenderChildrenPostRenderCache() {
$dom = Html::load($cached_child_element['#markup']);
$xpath = new \DOMXPath($dom);
- $child = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length;
- $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length;
+ $child = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]'))->length;
+ $subchild = $xpath->query($this->localizeXpath('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper" and text()="Subchild"]'))->length;
$this->assertTrue($child && $subchild, 'The correct data is cached for the child: the stored #markup is not affected by #post_render_cache callbacks.');
// Remove markup because it's compared above in the xpath.
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index e27b690..9e638cf 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -28,6 +28,7 @@
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\block\Entity\Block;
use Drupal\Core\Url;
+use Masterminds\HTML5;
use Symfony\Component\HttpFoundation\Request;
use Drupal\user\Entity\Role;
@@ -1862,8 +1863,8 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
);
// DOM can load HTML soup. But, HTML soup can throw warnings, suppress
// them.
- $dom = new \DOMDocument();
- @$dom->loadHTML($content);
+ $html5 = new HTML5();
+ $dom = $html5->loadHTML($content);
// XPath allows for finding wrapper nodes better than DOM does.
$xpath = new \DOMXPath($dom);
foreach ($ajax_response as $command) {
@@ -1888,12 +1889,11 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
// and 'body', since these are used by
// \Drupal\Core\Ajax\AjaxResponse::ajaxRender().
elseif (in_array($command['selector'], array('head', 'body'))) {
- $wrapperNode = $xpath->query('//' . $command['selector'])->item(0);
+ $wrapperNode = $xpath->query($this->localizeXpath('//' . $command['selector']))->item(0);
}
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
- $newDom = new \DOMDocument();
- @$newDom->loadHTML('' . $command['data'] . '
');
+ $newDom = $html5->loadHTML('' . trim($command['data']) . '
');
$newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
@@ -1943,14 +1943,14 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
case 'add_css':
break;
case 'update_build_id':
- $buildId = $xpath->query('//input[@name="form_build_id" and @value="' . $command['old'] . '"]')->item(0);
+ $buildId = $xpath->query($this->localizeXpath('//input[@name="form_build_id" and @value="' . $command['old'] . '"]'))->item(0);
if ($buildId) {
$buildId->setAttribute('value', $command['new']);
}
break;
}
}
- $content = $dom->saveHTML();
+ $content = $html5->saveHTML($dom);
$this->setRawContent($content);
$this->setDrupalSettings($drupal_settings);
}
@@ -2067,11 +2067,11 @@ protected function cronRun() {
*/
protected function checkForMetaRefresh() {
if (strpos($this->getRawContent(), 'parse()) {
- $refresh = $this->xpath('//meta[@http-equiv="Refresh"]');
- if (!empty($refresh)) {
+ preg_match('||', $this->getRawContent(), $matches);
+ if ($matches) {
// Parse the content attribute of the meta tag for the format:
// "[delay]: URL=[page_to_redirect_to]".
- if (preg_match('/\d+;\s*URL=(?.*)/i', $refresh[0]['content'], $match)) {
+ if (preg_match('/\d+;\s*URL=(?.*)/i', $matches[1], $match)) {
return $this->drupalGet($this->getAbsoluteUrl(String::decodeEntities($match['url'])));
}
}
@@ -2129,7 +2129,7 @@ protected function drupalHead($path, array $options = array(), array $headers =
*/
protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
// Retrieve the form elements.
- $elements = $form->xpath('.//input[not(@disabled)]|.//textarea[not(@disabled)]|.//select[not(@disabled)]');
+ $elements = $form->xpath($this->localizeXpath('.//input[not(@disabled)]|.//textarea[not(@disabled)]|.//select[not(@disabled)]'));
$submit_matches = FALSE;
foreach ($elements as $element) {
// SimpleXML objects need string casting all the time.
diff --git a/core/lib/Drupal/Component/XpathHelper/Lexer.php b/core/lib/Drupal/Component/XpathHelper/Lexer.php
new file mode 100644
index 0000000..8876aac
--- /dev/null
+++ b/core/lib/Drupal/Component/XpathHelper/Lexer.php
@@ -0,0 +1,240 @@
+ TRUE,
+ ']' => TRUE,
+ '=' => TRUE,
+ '(' => TRUE,
+ ')' => TRUE,
+ '.' => TRUE,
+ '<' => TRUE,
+ '>' => TRUE,
+ '*' => TRUE,
+ '+' => TRUE,
+ // Used in element names and functions. It's easier to just make a special
+ // case in the parser than to have the minus be a word boundary.
+ // '-' => TRUE,
+ '!' => TRUE,
+ '|' => TRUE,
+ ',' => TRUE,
+ ' ' => TRUE,
+ '"' => TRUE,
+ "'" => TRUE,
+ ':' => TRUE,
+ '::' => TRUE,
+ '/' => TRUE,
+ '//' => TRUE,
+ '@' => TRUE,
+ );
+
+ /**
+ * Lexes an XPath expression.
+ *
+ * @param string $expression
+ * An XPath expression.
+ *
+ * @return array
+ * A list of tokens from the XPath expression.
+ */
+ public function lex($expression) {
+ $this->expression = $expression;
+ $this->length = strlen($expression);
+ $this->cursor = 0;
+
+ $tokens = array();
+ while (TRUE) {
+ $token = $this->readToken();
+ if ($token === '') {
+ break;
+ }
+ $tokens[] = $token;
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Determines if a token is boundary for a word.
+ *
+ * @param string $token
+ * The token.
+ *
+ * @return bool
+ * Returns true if the token is a word boundary, and false if not.
+ */
+ public static function isWordBoundary($token) {
+ return isset(static::$wordBoundaries[$token]);
+ }
+
+ /**
+ * Reads the next token from the expression.
+ *
+ * @return string
+ * The next token, or an empty string on completion.
+ */
+ protected function readToken() {
+ while ($this->cursor < $this->length) {
+ $char = $this->expression[$this->cursor];
+
+ if ($char === '/') {
+ return $this->readOneOrTwoSlashes($char);
+ }
+
+ if ($char === '"' || $char === "'") {
+ return $this->consumeQuotes($char);
+ }
+
+ if ($char === ':') {
+ return $this->readNamespaceOrAxis();
+ }
+
+ if ($char === '@') {
+ return $this->readAttribute();
+ }
+
+ if ($this->isWordBoundary($char)) {
+ $this->cursor++;
+ return $char;
+ }
+
+ return $this->readWord();
+ }
+
+ return '';
+ }
+
+ /**
+ * Reads the next word from the expression.
+ *
+ * A word is considered anything that isn't a word boundary.
+ *
+ * @return string
+ * The next word.
+ */
+ protected function readWord() {
+ $word = '';
+
+ while ($this->cursor < $this->length) {
+ $char = $this->expression[$this->cursor];
+
+ // Found a boundary.
+ if ($this->isWordBoundary($char)) {
+ break;
+ }
+
+ $word .= $char;
+ $this->cursor++;
+ }
+
+ return $word;
+ }
+
+ /**
+ * Reads a quoted string from an XPath expression.
+ *
+ * @param string $start_quote
+ * The character that started the quoted string.
+ *
+ * @return string
+ * The quoted string.
+ */
+ protected function consumeQuotes($start_quote) {
+ $output = $start_quote;
+ do {
+ $next_char = $this->readNextChar();
+ $output .= $next_char;
+ } while ($next_char !== '' && $next_char !== $start_quote);
+
+ $this->cursor++;
+ return $output;
+ }
+
+ /**
+ * Reads a namespace token or an axis token.
+ *
+ * @return string
+ * Either a namespace separator or an axis separator. One or two colons.
+ */
+ protected function readNamespaceOrAxis() {
+ if ($this->readNextChar() === ':') {
+ $this->cursor++;
+ return '::';
+ }
+ return ':';
+ }
+
+ /**
+ * Reads on or two slashes.
+ *
+ * @return string
+ * Returns / or //.
+ */
+ protected function readOneOrTwoSlashes() {
+ if ($this->readNextChar() === '/') {
+ $this->cursor++;
+ return '//';
+ }
+ return '/';
+ }
+
+ /**
+ * Reads a shorthand attribute.
+ *
+ * @return string
+ * An attribute string starting with @.
+ */
+ protected function readAttribute() {
+ $this->cursor++;
+ return '@' . $this->readWord();
+ }
+
+ /**
+ * Returns the next character advancing the cursor.
+ *
+ * @return string
+ * The next character.
+ */
+ protected function readNextChar() {
+ $this->cursor++;
+ return isset($this->expression[$this->cursor]) ? $this->expression[$this->cursor] : '';
+ }
+
+}
diff --git a/core/lib/Drupal/Component/XpathHelper/Namespacer.php b/core/lib/Drupal/Component/XpathHelper/Namespacer.php
new file mode 100644
index 0000000..a5a2af7
--- /dev/null
+++ b/core/lib/Drupal/Component/XpathHelper/Namespacer.php
@@ -0,0 +1,351 @@
+ TRUE,
+ 'and' => TRUE,
+ 'div' => TRUE,
+ 'mod' => TRUE,
+ );
+
+ /**
+ * Tokens that come before elements.
+ *
+ * @var array
+ */
+ protected static $precedesElement = array(
+ '/' => TRUE,
+ '//' => TRUE,
+ '::' => TRUE,
+ '(' => TRUE,
+ ',' => TRUE,
+ '[' => TRUE,
+ );
+
+ /**
+ * Prefixes an XPath expression.
+ *
+ * Converts an expression from //div/a to //x:div/x:a.
+ *
+ * @param string $xpath
+ * The XPath expression to prefix.
+ * @param string $prefix
+ * (optional) The prefix to use. Defaults to "x".
+ *
+ * @return string
+ * The prefixed XPath expression.
+ */
+ public static function prefix($xpath, $prefix = 'x') {
+ if (!isset(static::$cache[$prefix][$xpath])) {
+ $parser = new static($xpath, $prefix, new Lexer());
+ static::$cache[$prefix][$xpath] = $parser->parse();
+ }
+
+ return static::$cache[$prefix][$xpath];
+ }
+
+ /**
+ * Localizes an XPath expression.
+ *
+ * Converts an expression from //div/a to
+ * //*[local-name() = "div"]/*[local-name() = "a"].
+ *
+ * @param string $xpath
+ * The XPath expression to prefix.
+ *
+ * @return string
+ * The localized XPath expression.
+ */
+ public static function localize($xpath) {
+ return static::prefix($xpath, NULL);
+ }
+
+ /**
+ * Constructs a Namespacer object.
+ *
+ * @param string $expression
+ * The XPath expression.
+ * @param string $prefix
+ * The prefix to use.
+ * @param \Drupal\Component\XpathHelper\Lexer $lexer
+ * The lexer that will produce tokens.
+ */
+ public function __construct($expression, $prefix, Lexer $lexer) {
+ $this->prefix = $prefix;
+ $this->tokens = $lexer->lex($expression);
+ }
+
+ /**
+ * Parses an XPath expression.
+ *
+ * @return string
+ * The rewritten XPath expression.
+ */
+ public function parse() {
+ $output = '';
+
+ $token_count = count($this->tokens);
+
+ for ( ; $this->cursor < $token_count; $this->cursor++) {
+ $token = $this->tokens[$this->cursor];
+
+ // A token that should be copied directly to the output.
+ if ($this->shouldCopy($token)) {
+ $output .= $token;
+ }
+ // A namespaced element.
+ elseif ($element = $this->getNamespacedElement($token)) {
+ $output .= $element;
+ }
+ // Namespace the element.
+ else {
+ $output .= $this->rewrite($token);
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Rewrites the token.
+ *
+ * Either in the form prefix:element or *[local-name() = "element"]
+ *
+ * @param string $token
+ * The element to rewrite.
+ *
+ * @return string
+ * The rewritten string.
+ */
+ protected function rewrite($element) {
+ if ($this->prefix) {
+ return $this->prefix . ':' . $element;
+ }
+ return '*[local-name() = "' . $element . '"]';
+ }
+
+ /**
+ * Determines if a token should be copied as-is to the output.
+ *
+ * @param string $token
+ * The token.
+ *
+ * @return bool
+ * Returns true if the token should be copied, and false if not.
+ */
+ protected function shouldCopy($token) {
+ if (Lexer::isWordBoundary($token)) {
+ return TRUE;
+ }
+ // Attribute or quoted string.
+ elseif ($token[0] === '@' || $token[0] === '"' || $token[0] === "'") {
+ return TRUE;
+ }
+ elseif (is_numeric($token) || is_numeric($token[0])) {
+ return TRUE;
+ }
+ elseif ($this->isFunctionCall()) {
+ return TRUE;
+ }
+ elseif ($this->isOperator($token)) {
+ return TRUE;
+ }
+ elseif ($this->isAxis()) {
+ return TRUE;
+ }
+ elseif ($this->wasAttributeAxis()) {
+ return TRUE;
+ }
+ // Handles the edge case where subtraction is written like 2 - 1.
+ elseif ($token === '-') {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Returns the namespaced element.
+ *
+ * @param string $token
+ * The token.
+ *
+ * @return string|bool
+ * The namespaced element, or false if it doesn't exist.
+ */
+ protected function getNamespacedElement($token) {
+ if ($this->peek(1) !== ':') {
+ return FALSE;
+ }
+
+ // Build the namespaced element, prefix:element.
+ $token .= ':' . $this->peek(2);
+ $this->cursor += 2;
+ return $token;
+ }
+
+ /**
+ * Determines if the current token is a function call.
+ *
+ * @param string $token
+ * The token.
+ *
+ * @return bool
+ * Returns true if the token is a function call and false if not.
+ */
+ protected function isFunctionCall() {
+ // Spaces before the opening parens of a function call are valid.
+ // Ex: //div[contains (@id, "thing")]
+ return $this->nextNonSpace() === '(';
+ }
+
+ /**
+ * Checks if a token is an operator, one of div, or, and, mod.
+ *
+ * @param string $token
+ * The token to check.
+ *
+ * @return bool
+ * Returns true if the token is an operator, and false if not.
+ */
+ protected function isOperator($token) {
+ if (!isset(static::$operators[$token])) {
+ return FALSE;
+ }
+
+ $prev = $this->prevNonSpace();
+ return $prev && !isset(static::$precedesElement[$prev]);
+ }
+
+ /**
+ * Determines whether this token is an axis.
+ *
+ * descendant-or-self, attribute, etc.
+ *
+ * @return bool
+ * True if the token is an axis, false if not.
+ */
+ protected function isAxis() {
+ return $this->nextNonSpace() === '::';
+ }
+
+ /**
+ * Determines whether the preceding token was an attribute axis.
+ *
+ * attribute::
+ *
+ * @return bool
+ * True if the preceding token was an attribute axis, false if not.
+ */
+ protected function wasAttributeAxis() {
+ return $this->prevNonSpace() === '::' && $this->prevNonSpace(2) === 'attribute';
+ }
+
+ /**
+ * Returns the next non-space token.
+ *
+ * @param int $delta
+ * (optional) The delta of the next non-space character. Defaults to 1.
+ *
+ * @return string
+ * The nth next non-space character.
+ */
+ protected function nextNonSpace($delta = 1) {
+ $n = 1;
+
+ for ($i = 0; $i < $delta; $i++) {
+ do {
+ $next = $this->peek($n);
+ $n++;
+ } while ($next === ' ');
+ }
+
+ return $next;
+ }
+
+ /**
+ * Returns the previous non-space token.
+ *
+ * @param int $delta
+ * (optional) The delta of the previous non-space character. Defaults to 1.
+ *
+ * @return string
+ * The nth previous non-space character.
+ */
+ protected function prevNonSpace($delta = 1) {
+ $n = -1;
+
+ for ($i = 0; $i < $delta; $i++) {
+ do {
+ $prev = $this->peek($n);
+ $n--;
+ } while ($prev === ' ');
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Returns a token from an offset of the current position.
+ *
+ * @param int $offset
+ * The offset from the current position.
+ *
+ * @return string
+ * Returns the token at the given offset.
+ */
+ protected function peek($offset) {
+ return isset($this->tokens[$this->cursor + $offset]) ? $this->tokens[$this->cursor + $offset] : '';
+ }
+
+}
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index e114ebf..942b3e1 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -10,6 +10,8 @@
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Xss;
+use Drupal\Component\XpathHelper\Namespacer;
+use Masterminds\HTML5;
use Symfony\Component\CssSelector\CssSelector;
/**
@@ -122,15 +124,22 @@ protected function setDrupalSettings($settings) {
*/
protected function parse() {
if (!isset($this->elements)) {
- // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
- // them.
- $html_dom = new \DOMDocument();
- @$html_dom->loadHTML('' . $this->getRawContent());
- if ($html_dom) {
- $this->pass(String::format('Valid HTML found on "@path"', array('@path' => $this->getUrl())), 'Browser');
+
+ // Check for XML preamble.
+ if (substr($this->getRawContent(), 0, 5) === 'loadXML($this->getRawContent());
+ }
+ else {
+ $html5 = new HTML5();
+ $dom = $html5->loadHTML($this->getRawContent());
+ }
+
+ if ($dom) {
+ $this->pass(String::format('Valid markup found on "@path"', array('@path' => $this->getUrl())), 'Browser');
// It's much easier to work with simplexml than DOM, luckily enough
// we can just simply import our DOM tree.
- $this->elements = simplexml_import_dom($html_dom);
+ $this->elements = simplexml_import_dom($dom);
}
}
if ($this->elements === FALSE) {
@@ -219,6 +228,14 @@ protected function buildXPathQuery($xpath, array $args = array()) {
protected function xpath($xpath, array $arguments = array()) {
if ($this->parse()) {
$xpath = $this->buildXPathQuery($xpath, $arguments);
+
+ // Register the default namespace if it exists.
+ $namespaces = $this->elements->getDocNamespaces();
+ if (!empty($namespaces[''])) {
+ $xpath = $this->prefixXpath($xpath);
+ $this->elements->registerXPathNamespace('x', $namespaces['']);
+ }
+
$result = $this->elements->xpath($xpath);
// Some combinations of PHP / libxml versions return an empty array
// instead of the documented FALSE. Forcefully convert any falsish values
@@ -231,6 +248,32 @@ protected function xpath($xpath, array $arguments = array()) {
}
/**
+ * Prefixes an xpath expression.
+ *
+ * @param string $xpath
+ * The xpath expression.
+ *
+ * @return string
+ * The prefixed xpath expression.
+ */
+ protected function prefixXpath($xpath) {
+ return Namespacer::prefix($xpath);
+ }
+
+ /**
+ * Localizes an xpath expression.
+ *
+ * @param string $xpath
+ * The xpath expression.
+ *
+ * @return string
+ * The localized xpath expression.
+ */
+ protected function localizeXpath($xpath) {
+ return Namespacer::localize($xpath);
+ }
+
+ /**
* Searches elements using a CSS selector in the raw content.
*
* The search is relative to the root element (HTML tag normally) of the page.
diff --git a/core/tests/Drupal/Tests/Component/XpathHelper/LexerTest.php b/core/tests/Drupal/Tests/Component/XpathHelper/LexerTest.php
new file mode 100755
index 0000000..4c4ca78
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/XpathHelper/LexerTest.php
@@ -0,0 +1,53 @@
+assertSame($expected, $lexer->lex($input));
+ }
+
+ /**
+ * Data provider for testLex().
+ *
+ * @return array
+ * - An xpath argument to lex().
+ * - Expected output from lex().
+ */
+ public function providerLex() {
+ return [
+ ['cat', ['cat']],
+ ['/cow/barn', ['/', 'cow', '/', 'barn']],
+ ['""', ['""']],
+ ['/cow/barn[@id = "asdfsaf"]', ['/', 'cow', '/', 'barn', '[', '@id', ' ', '=', ' ', '"asdfsaf"', ']']],
+ ['/cow/barn[@id=chair]', ['/', 'cow', '/', 'barn', '[', '@id', '=', 'chair', ']']],
+ ['/cow:asdf', ['/', 'cow', ':', 'asdf']],
+ ['@cow', ['@cow']],
+ ['starts-with(@id, "cat")', ['starts-with', '(', '@id' , ',', ' ', '"cat"', ')']],
+ ['starts-with(cat/dog/fire:breather, "cat")', ['starts-with', '(', 'cat', '/', 'dog', '/', 'fire' , ':', 'breather', ',', ' ', '"cat"', ')']],
+ ['child::book', ['child', '::', 'book']],
+ ["//a[@href='javascript:void(0)']", ['//', 'a', '[', '@href', '=', "'javascript:void(0)'", ']']],
+ ['1+1', ['1', '+', '1']],
+ ['//a[@id="id"and 1]', ['//', 'a', '[', '@id', '=', '"id"', 'and', ' ', '1', ']']],
+ ['0', ['0']],
+ ];
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/XpathHelper/NamespacerTest.php b/core/tests/Drupal/Tests/Component/XpathHelper/NamespacerTest.php
new file mode 100755
index 0000000..fa02cc8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/XpathHelper/NamespacerTest.php
@@ -0,0 +1,310 @@
+assertSame($expected, Namespacer::localize($xpath));
+ }
+
+ /**
+ * @covers ::prefix
+ * @dataProvider providerPrefixDefaultPrefix
+ */
+ public function testPrefixDefaultPrefix($xpath, $expected) {
+ $this->assertSame($expected, Namespacer::prefix($xpath));
+ }
+
+ /**
+ * Data provider for testPrefixDefaultPrefix().
+ *
+ * Gives us data which we can use to test prefix()'s default prefix.
+ *
+ * @return array
+ * - An xpath in need of a prefix.
+ * - The expected xpath with the default prefix added.
+ */
+ public function providerPrefixDefaultPrefix() {
+ $tests = [
+ ['cow', 'x:cow'],
+ ['/cow/barn', '/x:cow/x:barn'],
+ ['/cow/barn[@id = "asdfsaf"]', '/x:cow/x:barn[@id = "asdfsaf"]'],
+ ['/cow/barn [@id = "asdfsaf"]', '/x:cow/x:barn [@id = "asdfsaf"]'],
+ ['/cow/barn[@id=chair]', '/x:cow/x:barn[@id=x:chair]'],
+ ['/cow:asdf', '/cow:asdf'],
+ ['@cow', '@cow'],
+ ['starts-with(@id, "cat")', 'starts-with(@id, "cat")'],
+ ['starts-with(cat/dog/fire:breather, "cat")', 'starts-with(x:cat/x:dog/fire:breather, "cat")'],
+ ['//state[@id = ../city[name="CityName"]/state_id]/name', '//x:state[@id = ../x:city[x:name="CityName"]/x:state_id]/x:name'],
+ ['attribute::lang', 'attribute::lang'],
+ ['attribute:: lang', 'attribute:: lang'],
+ ['attribute ::lang', 'attribute ::lang'],
+ ['attribute :: lang', 'attribute :: lang'],
+ ['child::book', 'child::x:book'],
+ ['child :: book', 'child :: x:book'],
+ ['child::*', 'child::*'],
+ ['child:: *', 'child:: *'],
+ ['child ::*', 'child ::*'],
+ ['child :: *', 'child :: *'],
+ ['child::text()', 'child::text()'],
+ ['child::text ()', 'child::text ()'],
+ ['ancestor-or-self::book', 'ancestor-or-self::x:book'],
+ ['child::*/child::price', 'child::*/child::x:price'],
+ ["/asdfasfd[@id = 'a' or @id='b']", "/x:asdfasfd[@id = 'a' or @id='b']"],
+ ["id('yui-gen2')/x:div[3]/x:div/x:a[1]", "id('yui-gen2')/x:div[3]/x:div/x:a[1]"],
+ ["/descendant::a[@class='buttonCheckout']", "/descendant::x:a[@class='buttonCheckout']"],
+ ["//a[@href='javascript:void(0)']", "//x:a[@href='javascript:void(0)']"],
+ ['//*/@attribute', '//*/@attribute'],
+ ['/descendant::*[attribute::attribute]', '/descendant::*[attribute::attribute]'],
+ ['//Event[not(System/Level = preceding::Level) or not(System/Task = preceding::Task)]', '//x:Event[not(x:System/x:Level = preceding::x:Level) or not(x:System/x:Task = preceding::x:Task)]'],
+ ["section[@type='cover']/line/@page", "x:section[@type='cover']/x:line/@page"],
+ ['/articles/article/*[name()="title" or name()="short"]', '/x:articles/x:article/*[name()="title" or name()="short"]'],
+ ["/*/article[@id='2']/*[self::title or self::short]", "/*/x:article[@id='2']/*[self::x:title or self::x:short]"],
+ ['not(/asdfasfd/asdfasf//asdfasdf) | /asdfasf/sadfasf/@asdf', 'not(/x:asdfasfd/x:asdfasf//x:asdfasdf) | /x:asdfasf/x:sadfasf/@asdf'],
+ ['Ülküdak', 'x:Ülküdak'],
+ ['//textarea[@name="style[type]"]|//input[@name="style[type]"]|//select[@name="style[type]"]', '//x:textarea[@name="style[type]"]|//x:input[@name="style[type]"]|//x:select[@name="style[type]"]'],
+ ['//a[@id="id"and 1]', '//x:a[@id="id"and 1]'],
+ ['//*[@id and@class]', '//*[@id and@class]'],
+ ['/or', '/x:or'],
+ ['//and', '//x:and'],
+ ['div', 'x:div'],
+ ['a-1', 'x:a-1'],
+ ['//element [contains(@id, "1234")and contains(@id, 345)]', '//x:element [contains(@id, "1234")and contains(@id, 345)]'],
+ ['following-sibling::div', 'following-sibling::x:div'],
+ ['// div / a / @href', '// x:div / x:a / @href'],
+ ['a[contains(div, div)', 'x:a[contains(x:div, x:div)'],
+ ];
+
+ // Math related.
+ foreach (['+', '-', '*', '=', '!=', '<', '>', '<=', '>='] as $op) {
+ $tests[] = ["1{$op}2", "1{$op}2"];
+ $tests[] = ["1 {$op}2", "1 {$op}2"];
+ $tests[] = ["1{$op} 2", "1{$op} 2"];
+ $tests[] = ["1 {$op} 2", "1 {$op} 2"];
+ }
+
+ foreach (['and', 'or', 'mod', 'div'] as $op) {
+ $tests[] = ["1{$op} 2", "1{$op} 2"];
+ $tests[] = ["1 {$op} 2", "1 {$op} 2"];
+ }
+
+ return $tests;
+ }
+
+ /**
+ * Data provider for testParse().
+ *
+ * @return array
+ * - Expected parsed text.
+ * - Array of tokens to parse.
+ * - Return value for mocked shouldCopy().
+ * - Return value for mocked getNamespacedElement(). FALSE means return
+ * false, while any other value means return the string 'namespaced'.
+ */
+ public function providerParse() {
+ return [
+ ['', [], FALSE, FALSE],
+ ['', [], TRUE, TRUE],
+ ['token', ['token'], TRUE, TRUE],
+ ['tokentoken', ['token', 'token'], TRUE, TRUE],
+ ['namespaced', ['token'], FALSE, TRUE],
+ ['namespacednamespaced', ['token', 'token'], FALSE, TRUE],
+ ['rewritten', ['token'], FALSE, FALSE],
+ ['rewrittenrewritten', ['token', 'token'], FALSE, FALSE],
+ ];
+ }
+
+ /**
+ * @covers ::parse
+ * @dataProvider providerParse
+ */
+ public function testParse($expected, $token_array, $should_copy, $get_namespaced_element) {
+ // Create a mocked Namespacer object.
+ $mock_namespacer = $this->getMockBuilder('\Drupal\Component\XpathHelper\Namespacer')
+ ->disableOriginalConstructor()
+ ->setMethods(array('shouldCopy', 'getNamespacedElement', 'rewrite'))
+ ->getMock();
+
+ // Set expectations for shouldCopy(). It gets called on every token in the
+ // array.
+ $mock_namespacer->expects($this->exactly(count($token_array)))
+ ->method('shouldCopy')
+ ->willReturn($should_copy);
+
+ // Set expectations for getNamespacedElement(). It's called for any token
+ // in the array that shouldn't copy. We'll can just use $shouldCopy
+ // to turn expectations on or off.
+ $get_namespaced_element_count = 0;
+ if (!$should_copy) {
+ $get_namespaced_element_count = count($token_array);
+ }
+ // If getNamespacedElement() returns FALSE, we'll call rewrite(), so we have
+ // to manage that.
+ $get_namespaced_element_value = $get_namespaced_element;
+ if ($get_namespaced_element) {
+ $get_namespaced_element_value = 'namespaced';
+ }
+ // Finally assemble all this in the method.
+ $mock_namespacer->expects($this->exactly($get_namespaced_element_count))
+ ->method('getNamespacedElement')
+ ->willReturn($get_namespaced_element_value);
+
+ // Set expectations for rewrite().
+ $rewrite_count = 0;
+ if (!$should_copy && !$get_namespaced_element) {
+ $rewrite_count = count($token_array);
+ }
+ $mock_namespacer->expects($this->exactly($rewrite_count))
+ ->method('rewrite')
+ ->willReturn('rewritten');
+
+ // Set $tokens. $tokens is protected so we must use reflection.
+ $ref_tokens = new \ReflectionProperty($mock_namespacer, 'tokens');
+ $ref_tokens->setAccessible(TRUE);
+ $ref_tokens->setValue($mock_namespacer, $token_array);
+
+ // Set $cursor so our parse always starts from the beginning.
+ $ref_cursor = new \ReflectionProperty($mock_namespacer, 'cursor');
+ $ref_cursor->setAccessible(TRUE);
+ $ref_cursor->setValue($mock_namespacer, 0);
+
+ // Exercise parse().
+ $this->assertEquals($expected, $mock_namespacer->parse());
+ }
+
+ /**
+ * Data provider for testShouldCopy().
+ *
+ * The expectation of how many times a method will be called is encoded.
+ * Positive numbers are how many time the method will be called and will
+ * return TRUE. Negative numbers are the number of times the method will be
+ * callled and return FALSE.
+ *
+ * @return array
+ * - Expected bool result.
+ * - Token to test against.
+ * - (optional) Integer expectation of how many times isFunctionCall() will
+ * be called.
+ * - (optional) Integer expectation of how many times isOperator() will be
+ * called.
+ * - (optional) Integer expectation of how many times isAxis() will be
+ * called.
+ * - (optional) Integer expectation of how many times wasAttributeAxis()
+ * will be called.
+ */
+ public function providerShouldCopy() {
+ return [
+ // Values for Lexer::isWordBoundary().
+ [TRUE, '['],
+ [TRUE, '['],
+ [TRUE, ']'],
+ [TRUE, '='],
+ [TRUE, '('],
+ [TRUE, ')'],
+ [TRUE, '.'],
+ [TRUE, '<'],
+ [TRUE, '>'],
+ [TRUE, '*'],
+ [TRUE, '+'],
+ [TRUE, '!'],
+ [TRUE, '|'],
+ [TRUE, ','],
+ [TRUE, ' '],
+ [TRUE, '"'],
+ [TRUE, "'"],
+ [TRUE, ':'],
+ [TRUE, '::'],
+ [TRUE, '/'],
+ [TRUE, '//'],
+ [TRUE, '@'],
+ [TRUE, '@attribute'],
+ [TRUE, '"quoted"'],
+ [TRUE, "'quoted'"],
+ // Numeric.
+ [TRUE, '5'],
+ [TRUE, '23skidoo'],
+ // These hit our various method calls. They are named after the methods
+ // they hit, which happen to also be tokens that would return FALSE from
+ // shouldCopy().
+ [TRUE, 'isFunctionCall', 1],
+ [TRUE, 'isOperator', -1, 1],
+ [TRUE, 'isAxis', -1, -1, 1],
+ [TRUE, 'wasAttributeAxis', -1, -1, -1, 1],
+ // Special case for minus.
+ [TRUE, '-', -1, -1, -1, -1],
+ [FALSE, 'a token which should not be copied', -1, -1, -1, -1],
+ ];
+ }
+
+ /**
+ * @covers ::shouldCopy
+ * @dataProvider providerShouldCopy
+ */
+ public function testShouldCopy($expected, $token, $is_function_call = 0, $is_operator = 0, $is_axis = 0, $was_attribute_axis = 0) {
+ // Create a mocked Namespacer object.
+ $mock_namespacer = $this->getMockBuilder('\Drupal\Component\XpathHelper\Namespacer')
+ ->disableOriginalConstructor()
+ ->setMethods(['isFunctionCall', 'isOperator', 'isAxis', 'wasAttributeAxis'])
+ ->getMock();
+
+ // Set expectations for the dependency methods. The various parameters map
+ // out our expectations. The expectation of how many times a method will be
+ // called is encoded. Positive numbers are how many time the method will be
+ // called and will return TRUE. Negative numbers are the number of times the
+ // method will be callled and return FALSE.
+ $method_expectations_array = [
+ 'isFunctionCall' => $is_function_call,
+ 'isOperator' => $is_operator,
+ 'isAxis' => $is_axis,
+ 'wasAttributeAxis' => $was_attribute_axis,
+ ];
+ foreach ($method_expectations_array as $method => $expectation) {
+ $count = abs($expectation);
+ // Positive expectations return TRUE, negative expectations return FALSE.
+ $value = $expectation > 0;
+ $mock_namespacer->expects($this->exactly($count))
+ ->method($method)
+ ->willReturn($value);
+ }
+
+ // Since shouldCopy() is protected, we have to un-protect it.
+ $ref_should_copy = new \ReflectionMethod($mock_namespacer, 'shouldCopy');
+ $ref_should_copy->setAccessible(TRUE);
+
+ // Finally exercise shouldCopy().
+ $this->assertSame(
+ $expected,
+ $ref_should_copy->invoke($mock_namespacer, $token)
+ );
+ }
+
+}
diff --git a/core/composer.json b/core/composer.json
index 74e9bc9..1b76e06 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -27,7 +27,8 @@
"zendframework/zend-feed": "2.3.*",
"mikey179/vfsStream": "1.*",
"stack/builder": "1.0.*",
- "egulias/email-validator": "1.2.*"
+ "egulias/email-validator": "1.2.*",
+ "masterminds/html5": "~2.1"
},
"autoload": {
"psr-4": {
diff --git a/core/composer.lock b/core/composer.lock
index 9c24eb3..e90a7d2 100644
--- a/core/composer.lock
+++ b/core/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "c977649e8e1a8b93301fa83283672a06",
+ "hash": "18d8754c94d9038119bc5a03bcb83478",
"packages": [
{
"name": "doctrine/annotations",
@@ -729,6 +729,71 @@
"time": "2014-10-12 19:18:40"
},
{
+ "name": "masterminds/html5",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "a10f8d392e1aad0b500f7b440c8f0d3bc9189704"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/a10f8d392e1aad0b500f7b440c8f0d3bc9189704",
+ "reference": "a10f8d392e1aad0b500f7b440c8f0d3bc9189704",
+ "shasum": ""
+ },
+ "require": {
+ "ext-libxml": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*",
+ "sami/sami": "~2.0",
+ "satooshi/php-coveralls": "0.6.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Masterminds\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ }
+ ],
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "keywords": [
+ "HTML5",
+ "dom",
+ "html",
+ "parser",
+ "querypath",
+ "serializer",
+ "xml"
+ ],
+ "time": "2015-02-09 16:26:00"
+ },
+ {
"name": "mikey179/vfsStream",
"version": "v1.4.0",
"source": {
diff --git a/core/vendor/composer/ClassLoader.php b/core/vendor/composer/ClassLoader.php
index 70d78bc..5e1469e 100644
--- a/core/vendor/composer/ClassLoader.php
+++ b/core/vendor/composer/ClassLoader.php
@@ -54,6 +54,8 @@ class ClassLoader
private $useIncludePath = false;
private $classMap = array();
+ private $classMapAuthoritative = false;
+
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
@@ -249,6 +251,27 @@ public function getUseIncludePath()
}
/**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
@@ -299,6 +322,9 @@ public function findFile($class)
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
+ if ($this->classMapAuthoritative) {
+ return false;
+ }
$file = $this->findFileWithExtension($class, '.php');
diff --git a/core/vendor/composer/autoload_psr4.php b/core/vendor/composer/autoload_psr4.php
index 8b6150b..a75b107 100644
--- a/core/vendor/composer/autoload_psr4.php
+++ b/core/vendor/composer/autoload_psr4.php
@@ -8,6 +8,7 @@
return array(
'Symfony\\Cmf\\Component\\Routing\\' => array($vendorDir . '/symfony-cmf/routing'),
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
+ 'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
'GuzzleHttp\\Stream\\' => array($vendorDir . '/guzzlehttp/streams/src'),
'GuzzleHttp\\Ring\\' => array($vendorDir . '/guzzlehttp/ringphp/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json
index 2e99ee1..685e2f1 100644
--- a/core/vendor/composer/installed.json
+++ b/core/vendor/composer/installed.json
@@ -2735,5 +2735,72 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com"
+ },
+ {
+ "name": "masterminds/html5",
+ "version": "2.1.0",
+ "version_normalized": "2.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "a10f8d392e1aad0b500f7b440c8f0d3bc9189704"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/a10f8d392e1aad0b500f7b440c8f0d3bc9189704",
+ "reference": "a10f8d392e1aad0b500f7b440c8f0d3bc9189704",
+ "shasum": ""
+ },
+ "require": {
+ "ext-libxml": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*",
+ "sami/sami": "~2.0",
+ "satooshi/php-coveralls": "0.6.*"
+ },
+ "time": "2015-02-09 16:26:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Masterminds\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ }
+ ],
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "keywords": [
+ "HTML5",
+ "dom",
+ "html",
+ "parser",
+ "querypath",
+ "serializer",
+ "xml"
+ ]
}
]
diff --git a/core/vendor/masterminds/html5/.gitignore b/core/vendor/masterminds/html5/.gitignore
new file mode 100644
index 0000000..f9ead4a
--- /dev/null
+++ b/core/vendor/masterminds/html5/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+scratch.php
+composer.lock
+build/
\ No newline at end of file
diff --git a/core/vendor/masterminds/html5/.travis.yml b/core/vendor/masterminds/html5/.travis.yml
new file mode 100644
index 0000000..5b31d71
--- /dev/null
+++ b/core/vendor/masterminds/html5/.travis.yml
@@ -0,0 +1,28 @@
+language: php
+
+# Setting sudo access to false will let Travis CI use containers rather than
+# VMs to run the tests. For more details see:
+# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
+# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
+sudo: false
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm
+
+notifications:
+ irc: "irc.freenode.net#masterminds"
+
+before_script:
+ - composer self-update
+ - composer install --dev
+
+script:
+ - mkdir -p build/logs
+ - ./vendor/bin/phpunit -c phpunit.xml.dist
+
+after_script:
+ - php vendor/bin/coveralls -v
diff --git a/core/vendor/masterminds/html5/CREDITS b/core/vendor/masterminds/html5/CREDITS
new file mode 100644
index 0000000..c2dbc4b
--- /dev/null
+++ b/core/vendor/masterminds/html5/CREDITS
@@ -0,0 +1,11 @@
+Matt Butcher [technosophos] (lead)
+Matt Farina [mattfarina] (lead)
+Asmir Mustafic [goetas] (contributor)
+Edward Z. Yang [ezyang] (contributor)
+Geoffrey Sneddon [gsnedders] (contributor)
+Kukhar Vasily [ngreduce] (contributor)
+Rune Christensen [MrElectronic] (contributor)
+Mišo Belica [miso-belica] (contributor)
+Asmir Mustafic [goetas] (contributor)
+KITAITI Makoto [KitaitiMakoto] (contributor)
+Jacob Floyd [cognifloyd] (contributor)
diff --git a/core/vendor/masterminds/html5/LICENSE.txt b/core/vendor/masterminds/html5/LICENSE.txt
new file mode 100644
index 0000000..3c275b5
--- /dev/null
+++ b/core/vendor/masterminds/html5/LICENSE.txt
@@ -0,0 +1,66 @@
+## HTML5-PHP License
+
+Copyright (c) 2013 The Authors of HTML5-PHP
+
+Matt Butcher - mattbutcher@google.com
+Matt Farina - matt@mattfarina.com
+Asmir Mustafic - goetas@gmail.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+## HTML5Lib License
+
+Portions of this are based on html5lib's PHP version, which was a
+sub-project of html5lib. The following is the list of contributors from
+html5lib:
+
+html5lib:
+
+Copyright (c) 2006-2009 The Authors
+
+Contributors:
+James Graham - jg307@cam.ac.uk
+Anne van Kesteren - annevankesteren@gmail.com
+Lachlan Hunt - lachlan.hunt@lachy.id.au
+Matt McDonald - kanashii@kanashii.ca
+Sam Ruby - rubys@intertwingly.net
+Ian Hickson (Google) - ian@hixie.ch
+Thomas Broyer - t.broyer@ltgt.net
+Jacques Distler - distler@golem.ph.utexas.edu
+Henri Sivonen - hsivonen@iki.fi
+Adam Barth - abarth@webkit.org
+Eric Seidel - eric@webkit.org
+The Mozilla Foundation (contributions from Henri Sivonen since 2008)
+David Flanagan (Mozilla) - dflanagan@mozilla.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/core/vendor/masterminds/html5/README.md b/core/vendor/masterminds/html5/README.md
new file mode 100644
index 0000000..1a2a96b
--- /dev/null
+++ b/core/vendor/masterminds/html5/README.md
@@ -0,0 +1,253 @@
+# HTML5-PHP
+
+The need for an HTML5 parser in PHP is clear. This project initially
+began with the seemingly abandoned `html5lib` project [original source](https://code.google.com/p/html5lib/source/checkout).
+But after some initial refactoring work, we began a new parser.
+
+- An HTML5 serializer
+- Support for PHP namespaces
+- Composer support
+- Event-based (SAX-like) parser
+- DOM tree builder
+- Interoperability with QueryPath [[in progress](https://github.com/technosophos/querypath/issues/114)]
+- Runs on **PHP** 5.3.0 or newer and **HHVM** 3.2 or newer
+
+[![Build Status](https://travis-ci.org/Masterminds/html5-php.png?branch=master)](https://travis-ci.org/Masterminds/html5-php) [![Latest Stable Version](https://poser.pugx.org/masterminds/html5/v/stable.png)](https://packagist.org/packages/masterminds/html5) [![Coverage Status](https://coveralls.io/repos/Masterminds/html5-php/badge.png?branch=master)](https://coveralls.io/r/Masterminds/html5-php?branch=master)
+
+## Installation
+
+Install HTML5-PHP using [composer](http://getcomposer.org/).
+
+To install, add `masterminds/html5` to your `composer.json` file:
+
+```
+{
+ "require" : {
+ "masterminds/html5": "2.*"
+ },
+}
+```
+
+(You may substitute `2.*` for a more specific release tag, of
+course.)
+
+From there, use the `composer install` or `composer update` commands to
+install.
+
+## Basic Usage
+
+HTML5-PHP has a high-level API and a low-level API.
+
+Here is how you use the high-level `HTML5` library API:
+
+```php
+
+
+ TEST
+
+
+ Hello World
+ This is a test of the HTML5 parser.
+
+