master 9f889148e8b3 cached
156 files
15.4 MB
4.0M tokens
262 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (16,185K chars total). Download the full file to get everything.
Repository: serp-spider/search-engine-google
Branch: master
Commit: 9f889148e8b3
Files: 156
Total size: 15.4 MB

Directory structure:
gitextract_vwfvhtj1/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build/
│   └── .gitignore
├── composer.json
├── phpcs.xml
├── phpunit.dist.xml
├── src/
│   ├── AdwordsResultItem.php
│   ├── AdwordsResultType.php
│   ├── AdwordsSectionResultSet.php
│   ├── Exception/
│   │   ├── GoogleCaptchaException.php
│   │   └── InvalidDOMException.php
│   ├── GoogleClient.php
│   ├── GoogleUrl.php
│   ├── GoogleUrlArchive.php
│   ├── GoogleUrlInterface.php
│   ├── GoogleUrlTrait.php
│   ├── NaturalResultType.php
│   ├── Page/
│   │   ├── GoogleCaptcha.php
│   │   ├── GoogleDom.php
│   │   ├── GoogleError.php
│   │   ├── GoogleSerp.php
│   │   └── NotFound.php
│   └── Parser/
│       ├── AbstractAdwordsParser.php
│       ├── AbstractParser.php
│       ├── Evaluated/
│       │   ├── AdwordsParser.php
│       │   ├── AdwordsSectionParser.php
│       │   ├── MobileAdwordsParser.php
│       │   ├── MobileAdwordsSectionParser.php
│       │   ├── MobileNaturalParser.php
│       │   ├── NaturalParser.php
│       │   └── Rule/
│       │       ├── Adwords/
│       │       │   ├── AdwordsItem.php
│       │       │   ├── AdwordsItemMobile.php
│       │       │   └── Shopping.php
│       │       └── Natural/
│       │           ├── AnswerBox.php
│       │           ├── Classical/
│       │           │   ├── ClassicalCardsResult.php
│       │           │   ├── ClassicalCardsResultO9g5cc.php
│       │           │   ├── ClassicalCardsResultZ1m.php
│       │           │   ├── ClassicalCardsResultZINbbc.php
│       │           │   ├── ClassicalCardsVideoResult.php
│       │           │   ├── ClassicalResult.php
│       │           │   └── ClassicalWithLargeVideo.php
│       │           ├── ComposedTopStories.php
│       │           ├── Divider.php
│       │           ├── Flight.php
│       │           ├── ImageGroup.php
│       │           ├── ImageGroupCarousel.php
│       │           ├── InTheNews.php
│       │           ├── KnowledgeCard.php
│       │           ├── Map.php
│       │           ├── MapLegacy.php
│       │           ├── MapMobile.php
│       │           ├── PeopleAlsoAsk.php
│       │           ├── SearchResultGroup.php
│       │           ├── TopStoriesCarousel.php
│       │           ├── TopStoriesVertical.php
│       │           ├── TweetsCarousel.php
│       │           ├── TweetsCarouselZ1m.php
│       │           └── VideoGroup.php
│       ├── ParserInterface.php
│       └── ParsingRuleInterface.php
├── stubs/
│   └── RelatedSearch.php
└── test/
    ├── bin/
    │   ├── ci.bash
    │   ├── phpcbf.bash
    │   ├── phpcs.bash
    │   └── test.bash
    ├── resources/
    │   ├── pages-evaluated/
    │   │   ├── 2018/
    │   │   │   ├── 03/
    │   │   │   │   ├── asian+massage.html
    │   │   │   │   ├── plumber+london.html
    │   │   │   │   ├── qui+est+homer+simpsons.html
    │   │   │   │   └── super+u+paris.html
    │   │   │   ├── 07/
    │   │   │   │   └── foo+bar(page2).html
    │   │   │   ├── 09/
    │   │   │   │   └── 65b6be0a-7619-4018-97c9-989cdec53319.html
    │   │   │   ├── 10/
    │   │   │   │   ├── agence+web+nantes.html
    │   │   │   │   └── who+is+homer+simpson.html
    │   │   │   └── 11/
    │   │   │       └── acheter+kobo.html
    │   │   ├── 2019/
    │   │   │   ├── 05/
    │   │   │   │   └── plombier+paris.html
    │   │   │   └── 07/
    │   │   │       └── cheap+video+editing+software+mac.html
    │   │   ├── adwords/
    │   │   │   └── simpsons+poster.html
    │   │   ├── alarmas+para+casa.html
    │   │   ├── captcha.html
    │   │   ├── cards-design.html
    │   │   ├── flights.html
    │   │   ├── github(with-vertical-top-stories).html
    │   │   ├── github.html
    │   │   ├── how+is+homer+simpsons.html
    │   │   ├── inde(top-stories).html
    │   │   ├── narendra+modi.html
    │   │   ├── page-with-bkgrouped-results.html
    │   │   ├── ransomware.html
    │   │   ├── shop-near-paris.html
    │   │   ├── simpsons(related).html
    │   │   ├── simpsons+donut.html
    │   │   ├── simpsons+movie+trailer.html
    │   │   ├── simpsons.html
    │   │   ├── simpsonsworld.html
    │   │   └── with-DOMText.html
    │   ├── pages-mobile/
    │   │   ├── 2017/
    │   │   │   ├── 11/
    │   │   │   │   ├── buy+pen.html
    │   │   │   │   └── donald+trump.html
    │   │   │   └── 12/
    │   │   │       ├── foo.html
    │   │   │       └── who+is+homer+simpsons.html
    │   │   ├── 2018/
    │   │   │   ├── 03/
    │   │   │   │   └── foo.html
    │   │   │   ├── 07/
    │   │   │   │   └── simpsons-episode-1.html
    │   │   │   ├── 08/
    │   │   │   │   └── new+construction+ct.html
    │   │   │   └── 11/
    │   │   │       └── 01/
    │   │   │           └── plombier+nantes.html
    │   │   ├── simpsons+donuts.html
    │   │   ├── simpsons+homer.html
    │   │   └── simpsons+world.html
    │   ├── pages-raw/
    │   │   └── simpsons.html
    │   └── simple-dom.html
    └── suites/
        ├── AdwordsResultItemTest.php
        ├── AdwordsSectionResultSetTest.php
        ├── GoogleClientTest.php
        ├── GoogleSerpTestCase.php
        ├── GoogleUrlTest.php
        ├── Page/
        │   ├── GoogleCaptchaTest.php
        │   ├── GoogleDomTest.php
        │   └── GoogleSerpTest.php
        └── Parser/
            └── Evaluated/
                ├── AdwordsParserTest.php
                ├── NaturalParserTest.php
                └── natural-parser-data/
                    ├── 2018/
                    │   ├── 03/
                    │   │   ├── asian+massage.yml
                    │   │   ├── plumber+london.yml
                    │   │   ├── qui+est+homer+simpsons.yml
                    │   │   └── super+u+paris.yml
                    │   ├── 07/
                    │   │   └── foo+bar(page2).yml
                    │   ├── 09/
                    │   │   └── 65b6be0a-7619-4018-97c9-989cdec53319.yml
                    │   ├── 10/
                    │   │   ├── agence+web+nantes.yml
                    │   │   └── who+is+homer+simpson.yml
                    │   └── 11/
                    │       └── acheter+kobo.yml
                    ├── 2019/
                    │   ├── 05/
                    │   │   └── plombier+paris.yml
                    │   └── 07/
                    │       └── cheap+video+editing+software+mac.yml
                    ├── github(with-vertical-top-stories).yml
                    ├── inde(top-stories).yml
                    ├── mobile/
                    │   ├── 2017/
                    │   │   ├── 11/
                    │   │   │   ├── buy+pen.yml
                    │   │   │   └── mobile-donald+trump.yml
                    │   │   └── 12/
                    │   │       ├── foo.yml
                    │   │       └── who+is+homer+simpsons.yml
                    │   └── 2018/
                    │       ├── 03/
                    │       │   └── foo.yml
                    │       ├── 07/
                    │       │   └── simpsons-episode-1.yml
                    │       ├── 08/
                    │       │   └── new-construction-ct.yml
                    │       └── 11/
                    │           └── 01/
                    │               └── plombier+nantes.yml
                    ├── mobile-simpsons+donuts.yml
                    ├── mobile-simpsons+homer.yml
                    ├── mobile-simpsons+world.yml
                    ├── narendra+modi.yml
                    ├── natural-cards.yml
                    ├── naturals-data-with_bkgroups.yml
                    ├── naturals-data.yml
                    ├── ransomware.yml
                    ├── simpsons+donuts.yml
                    ├── simpsons+movie+trailer.yml
                    └── simpsonsworld.yml

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org

root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.json]
indent_size = 2

[*.yml]
indent_size = 2

[*.css]
indent_size = 2

[*.scss]
indent_size = 2



================================================
FILE: .gitattributes
================================================
/build              export-ignore
/script             export-ignore
/test               export-ignore
.codeclimate.yml    export-ignore
.editorconfig       export-ignore
.gitattributes      export-ignore
.gitignore          export-ignore
.travis.yml         export-ignore
phpunit.dist.xml    export-ignore
phpcs.xml           export-ignore

/test/resources/*   linguist-vendored


================================================
FILE: .gitignore
================================================
composer.lock
/vendor
.idea


================================================
FILE: .travis.yml
================================================
language: php

dist: trusty
sudo: false

matrix:
  include:
  - php: 5.5
    env: PROCESS_CODECLIMATE=true
  - php: 5.6
  - php: 7.0
  - php: 7.1
  - php: nightly
  - php: hhvm
    env: IGNORE_XDEBUG=true

  fast_finish: true

  allow_failures:
  - php: nightly

before_script:
  - if [ -z "$IGNORE_XDEBUG" ];then phpenv config-rm xdebug.ini; fi
  - travis_retry composer self-update
  - travis_retry composer update --prefer-dist
  - if [ -n "$PROCESS_CODECLIMATE" ];then echo 'zend_extension=xdebug.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi

script: ./test/bin/ci.bash summary

env:
  global:
    secure: kBRY5+RxotmB1NwI1GVJ9ndgsANkyuvMSrYtaDqYFdLo5c5Eow0HE+g8hpTkUCGethUz+/b5UzXl/w+ocCl3n4T17MSceLy1cY0gCTG+drRV+vAn8SOHx6QWB2Zp2ixFtQrv6ivOHq8Rjh244jkyDZrnNRjMHUkdUteSOC10o1bBzNpZ2iNR/Tnzr1uU4MilRCq9u2/QtrPZppu2ki0/h0vtb8K0Zi84S8fWHP+Qf8hMgsGTnOl3jhQHwEA/C+OEHAt3V0cxHIZ/rV6CC6k1c91XvCk4kI0gk2C14DIax5TDtvxif+SmlB2nDjRYbeHyTLuiXxJAXttjQ7mWB4w0D3YtxxLLu3h7CPnZjp+BF6KVLfXWu1T2+/WD/snvGChqkwLB/Z2VsTbxzYlXbZSIp13xXDKJa16jH2tWl3mr6o0UsuZq46zBERemoL3LWzj1wy6o2XJvSrbir/AaLZI5OADYhK7ZST95aHnm9QykKQzGNWVBuvYBPjIOQIrqnRGZSBQWMqVZQLjhEr7TR2GenGRdE8gvh3gWbP2yO8sCUUZO7klDSIrnIgMjvawtDautixoqmH9wlXUZgX1H8K2mBWc7ZoxIaVFBUhrv7+fHKM3zoqg6cBXhhAtgYQu5YieaZv9gnl8B77arfriARMAsKsn0Wh/nZP40AwUAk+ZFSps=

cache:
  directories:
    - $HOME/.composer/cache


================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG

## 0.4.7

*2018-18-05*

* Bug fix: 
    * Fixed title for adwords results

## 0.4.6

*2018-30-10*

* Feature: 
    * Added adwords results for mobiles
    * Added map results for mobiles
* Bug fix:
    * Fixed description for mobile classical
    * Fixed related searches for mobile
* Other:
    * Added ``ext-dom`` in composer.json


## 0.4.5

*2018-30-10*

* Bug fix:
    * Fixed local pack (#113) Thanks @Human018
    * Fixed urls for ads results

## 0.4.4

*2018-10-22*

* Bug fix:
    * Fixed people also ask update

## 0.4.3

*2018-09-17*

* Bug fix:
    * Fix google dom update on classical results

## 0.4.2

*2018-08-05*

* Bug fix:
    * fix mobile serps (#106)


## 0.4.1

*2018-07-05* 

* Bug fix:
    * fixed multiple mobile issues on mobile results
    * fixed parsing for number of results (#100) - thanks @migliori
    * fixed related searches on desktop  - thanks @gudix

## 0.4.0

*2018-05-29*

* Bug fix:
    * fixed the captcha exception. The right exception is now returned when a captcha is found
    * fixed invalid type hinting causing errors with hhvm
* Google updates:
    * **bc break** removed support for image captcha as google now uses recaptcha
* Other:
    * When an invalid classical result is found, throw an exception instead of returning invalid results causing fatal errors.

## 0.3.0 

*2018-04-04*

* Dependencies
    * **bc break** use version ``0.3.x`` of ``serps/core``
* Updates
    * **bc break** google url default domain is now ``"www.google.com`` instead of ``google.com``. This way we avoid extra redirects too the ``"www"`` subdomain.
    * Fix a bug with search result group parser that was triggering a php error.
* Dom Updates
    * Fix parsing for classical results on mobiles.
    * Fix parsing for knowledge cards on mobiles.

## 0.2.5

*2018-03-29*

* Bug fix:
    * Fix a bug with map results introduced in version 0.2.4 see [#94](https://github.com/serp-spider/search-engine-google/issues/94)

## 0.2.4

*2018-03-22*

* Bug fix:
    * Fix google update for map results
    * Fix google update for "destination" data in classical results
    * Fix google update for People Also Ask
    * Fix google update for answer box [#90](https://github.com/serp-spider/search-engine-google/issues/90)
    
## 0.2.3

*2017-12-11*

* Features:
    * Added parsing for people also ask results [#70](https://github.com/serp-spider/search-engine-google/issues/70)

* Bug fix:
    * Fix some mobile card results not parsing [#83](https://github.com/serp-spider/search-engine-google/issues/83)

## 0.2.2

*2017-11-25*

* Bug fix:
    * Parse ``bkWMgd`` groups (thanks to [Shiftas](https://github.com/Shiftas)) [#76](https://github.com/serp-spider/search-engine-google/issues/76)
    * Fix result count [#76](https://github.com/serp-spider/search-engine-google/issues/76)
    * Fix some mobile card results not parsing [#79](https://github.com/serp-spider/search-engine-google/issues/79) and [#78](https://github.com/serp-spider/search-engine-google/issues/78)
    * Fix twitter carousel parser for mobile [#81](https://github.com/serp-spider/search-engine-google/issues/81)
    * Fix related searches for mobile [#80](https://github.com/serp-spider/search-engine-google/issues/81)

* Features:
    * Parsing for "composed top stories" and standardizing old "top stories" [#67](https://github.com/serp-spider/search-engine-google/issues/67)

* Other:
    * Dependency to serps/core was updated from ~0.2.0 to ~0.2.4

## 0.2.1

*2017-07-16*

* Features:
    * Parsing for mobile knowledge results (fd95ffc07c137223e36fade739b4617c17fe6758)

* Bug fix
    * Fixing tweet carousel recognition (4f681da0435454b5ff592c657789010ccf8361ee)
    * Fixing tweet carousel non linked to an user


## 0.2.0 

*2017-05-01*

* Breaking Changes:
    * Images data are returned MediaInterface [#35](https://github.com/serp-spider/search-engine-google/issues/35)
    * Drop support for raw parser [5f41ddeb6a9076b363a83071e0f27a0254f1e330](https://github.com/serp-spider/search-engine-google/commit/5f41ddeb6a9076b363a83071e0f27a0254f1e330)
    * ``Serps\SearchEngine\Google\GoogleDom`` now extends ``Serps\Core\Dom\WebPage`` [dafe67e](https://github.com/serp-spider/search-engine-google/commit/dafe67eeae3eb46bb570fdc3eadd22d4abe47b7d)
    * ``Serps\SearchEngine\Google\GoogleError`` now extends ``Serps\Core\Dom\WebPage`` 
    and does not extend ``Serps\SearchEngine\Google\GoogleDom`` anymore [dafe67e](https://github.com/serp-spider/search-engine-google/commit/dafe67eeae3eb46bb570fdc3eadd22d4abe47b7d)
    * Class ``Serps\SearchEngine\Google\Css`` was removed and an equivalent is now provided from the core package in
    ``Serps\Core\Dom\Css`` [4e5b1a1](https://github.com/serp-spider/search-engine-google/commit/4e5b1a193abfe5093a48152b12878e7cef022b7b)
    * Vendor ``symfony/css-selector`` is not provided anymore, instead it moved in core package [4e5b1a1](https://github.com/serp-spider/search-engine-google/commit/4e5b1a193abfe5093a48152b12878e7cef022b7b)
    * ``GoogleClient::query($googleUrl, $proxy, $cookieJar)`` was refactored 
    to ``GoogleClient::query($googleUrl, $browser)`` in order to provide a more fluent management
    of browser specifications [a6fe671](https://github.com/serp-spider/search-engine-google/commit/a6fe6711d6fac42977cfc30212e438d8ab933584)
    * ``GoogleClient::query`` does not auto set language header anymore, that's now done from the browser instance [a6fe671](https://github.com/serp-spider/search-engine-google/commit/a6fe6711d6fac42977cfc30212e438d8ab933584)
    * ``GoogleClient::request`` and ``GoogleClient::getRequestBuilder()`` were removed and are replaced with
    browser implementation [a6fe671](https://github.com/serp-spider/search-engine-google/commit/a6fe6711d6fac42977cfc30212e438d8ab933584)
    * class ``Serps\SearchEngine\Google\GoogleClient\RequestBuilder`` was removed
    * fix the typo in the interface name ``ParsingRuleInterace`` that is now ``ParsingRuleInterface``
    * Method ``ParsingRuleInterace::match(GoogleDom $dom, \DOMElement $node)`` 
    is now ``ParsingRuleInterace::match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)``
    * the property ``is_carousel`` from top stories is now named ``isCarousel``
    
* Features:
    * Google cards results are now supported [#38](https://github.com/serp-spider/search-engine-google/pull/38)
    * Mobile page detection: GoogleSerp::isMobile() [564057ce0ee255cfa138440e033776b85f239acb](https://github.com/serp-spider/search-engine-google/commit/564057ce0ee255cfa138440e033776b85f239acb)
    * Mobile results have now their own parser
    * Parsing rule for mobile video groups [#41](https://github.com/serp-spider/search-engine-google/issues/41)
    * Parsing rule for mobile image groups
* Bug fixes:
    * Large video have the CLASSICAL type as mentioned in the doc [#36](https://github.com/serp-spider/search-engine-google/issues/36)


================================================
FILE: CONTRIBUTING.md
================================================
CONTRIBUTING
============

Any contribution is welcome.


Issue
-----

If you encounter an issue with the library you are encouraged to report it and we can work together on solving it.

Before submitting the issue please make sure you are using the latest version of serps/search-engine-google. 
If you are and if you still have the issue you can report it 
on the [issue tracker](https://github.com/serp-spider/search-engine-google/issues/new).

When reporting an issue try to provide as much details as possible. If the issue is related to a page that cannont
parse correctly, the following details will be very helpful to fix the issue:

- The url that fails to parse
- What is expected
- What you are getting
- You might attach the html of the page that does not parse 
- You might send screenshot of what is notparsing correctly


Code contribution
-----------------

### Tests

All contributions must be tested following as much as possible the current test structure.
Look at current tests in ``test/suites`` for more details.

### Coding Standards

The code follows the PSR-2 coding standards. 
We provided two useful commands to check and fix automatically code standards:

- Checking standards: ``composer cscheck``
- Fixing standards: ``composer csfix``


### Tools

- run full test suit: ``composer test``
- run some tests only: ``composer test testName`` 
(``testName`` will be used in [phpunit --filter](https://phpunit.de/manual/current/en/textui.html#textui.examples.filter-patterns))


================================================
FILE: LICENSE
================================================
Copyright (c) 2015-today, Soufiane GHZAL <sghzal@gmail.com> and contributors

Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.


================================================
FILE: README.md
================================================
SERPS - Search Engine: Google
=============================

[![Build Status](https://travis-ci.org/serp-spider/search-engine-google.svg?branch=master)](https://travis-ci.org/serp-spider/search-engine-google)
[![Test Coverage](https://codeclimate.com/github/serp-spider/search-engine-google/badges/coverage.svg)](https://codeclimate.com/github/serp-spider/search-engine-google/coverage)
[![Latest Stable Version](https://poser.pugx.org/serps/search-engine-google/version)](https://packagist.org/packages/serps/search-engine-google)
[![License](https://poser.pugx.org/serps/search-engine-google/license)](https://packagist.org/packages/serps/search-engine-google)


This is the Google implementation for [SERPS](https://github.com/serp-spider/serps)

Install
-------

Install it through [composer](https://getcomposer.org/) with the package 
[serps/search-engine-google](https://packagist.org/packages/serps/search-engine-google) : 

``composer require  --prefer-dist 'serps/search-engine-google'``

Note: it is recommended to use ``--prefer-dist`` to avoid downloading html test files.

Documentation
-------------

Browse the [documentation](http://serp-spider.github.io/documentation/search-engine/google/)



Disclaimer
-----------

> Using our Services does not give you ownership of any intellectual property rights in 
> our Services or the content you access. 
> You may not use content from our Services unless you obtain permission from its owner or 
> are otherwise permitted by law
>
> Extract from [Google terms of services](https://www.google.com/policies/terms/)

When using this software must respect terms of services of third parties like Google.
Serps authors and contributors cannot be hold as liable for the use you make of this software. 


================================================
FILE: build/.gitignore
================================================
*
!.gitignore


================================================
FILE: composer.json
================================================
{

  "name": "serps/search-engine-google",
  "description": "Google Rules and client for SERPS",
  "type": "library",
  "keywords": ["SERPS", "Google"],
  "homepage": "https://github.com/serp-spider/search-engine-google",
  "license": "Fair",

  "minimum-stability": "dev",
  "prefer-stable": true,

  "authors": [
    {
      "name": "Soufiane GHZAL",
      "homepage": "https://github.com/gsouf"
    }
  ],

  "autoload":{
    "psr-4" : {
      "Serps\\SearchEngine\\Google\\": "src/"
    }
  },

  "autoload-dev":{
    "psr-4" : {
      "Serps\\Test\\SearchEngine\\Google\\": "test/suites"
    }
  },

  "require": {
    "php": ">=5.5",
    "serps/core": "~0.3.0",
    "ext-dom": "*"
  },

  "require-dev":{
    "phpunit/phpunit": "~4.1",
    "symfony/yaml": ">=2.0",
    "squizlabs/php_codesniffer": "~3.2",
    "guzzlehttp/psr7": "^1.4",
    "serps/cli": "^1.1"
  },

  "suggest": {
    "zendframework/zend-diactoros": "For http request",
    "guzzlehttp/psr7": "For http request"
  },

  "scripts": {
    "phpunit": "test/bin/test.bash",
    "test": [
      "@phpunit",
      "@cscheck"
    ],
    "csfix": "test/bin/phpcbf.bash",
    "cscheck": "test/bin/phpcs.bash emacs"
  },

  "extra": {
    "branch-alias": {
      "dev-master": "0.3.0-dev"
    }
  }
}


================================================
FILE: phpcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="gsouf">

    <file>./src/</file>
    <file>./test/suites</file>

    <!--The complete PSR-2 ruleset-->
    <rule ref="PSR2"/>

    <!-- Arrays -->
    <rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
    <rule ref="Squiz.Arrays.ArrayBracketSpacing"/>

    <!-- Long lines do no apply on test files -->
    <rule ref="Generic.Files.LineLength.TooLong">
        <exclude-pattern>./test/*</exclude-pattern>
        <severity>0</severity>
    </rule>
    <!-- When test method have weird signatures -->
    <rule ref="PEAR.Functions.ValidDefaultValue.NotAtEnd">
        <exclude-pattern>./test/*</exclude-pattern>
    </rule>

    <!-- By default we use single quote only. Double quotes come when a variable is inside -->
    <rule ref="Squiz.Strings.DoubleQuoteUsage">
        <exclude name="Squiz.Strings.DoubleQuoteUsage.ContainsVar"/>
    </rule>

    <!-- Disallow some function like var_dump -->
    <rule ref="Generic.PHP.ForbiddenFunctions">
        <properties>
            <property name="forbiddenFunctions" type="array" value="var_dump=>null,sizeof=>count,delete=>unset,echo=>null"/>
        </properties>
    </rule>


</ruleset>


================================================
FILE: phpunit.dist.xml
================================================
<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         forceCoversAnnotation="true"
         backupStaticAttributes="false"
         colors="true"
         verbose="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">

    <testsuites>
        <testsuite>
            <directory>test/suites</directory>
        </testsuite>
    </testsuites>


    <filter>
        <blacklist>
            <directory>./vendor</directory>
            <directory>./doc</directory>
            <directory>./script</directory>
            <directory>./test</directory>
        </blacklist>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./src</directory>
        </whitelist>
    </filter>

    <logging>
        <log type="coverage-html" target="build/logs/html" lowUpperBound="35" highLowerBound="70"/>
        <log type="coverage-clover" target="build/logs/clover.xml"/>
        <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
    </logging>

</phpunit>


================================================
FILE: src/AdwordsResultItem.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\ProxyResult;
use Serps\Core\Serp\ResultDataInterface;

class AdwordsResultItem extends ProxyResult
{

    protected $location;


    /**
     * AdwordsResultItem constructor.
     * @param string $location
     * @param ResultDataInterface $itemData
     */
    public function __construct($location, ResultDataInterface $itemData)
    {
        $this->location = $location;
        parent::__construct($itemData);
    }

    public function getTypes()
    {
        $types = parent::getTypes();
        $types[] = $this->location;
        return $types;
    }

    public function is($types)
    {
        $types = func_get_args();
        if (in_array($this->location, $types)) {
            return true;
        }

        return call_user_func_array(['parent', 'is'], $types);
    }
}


================================================
FILE: src/AdwordsResultType.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

class AdwordsResultType
{

    const SECTION_TOP = 'adws_section_top';
    const SECTION_BOTTOM = 'adws_section_bottom';
    const SECTION_RIGHT = 'adws_section_right';

    const AD = 'adws_ad';
    const SHOPPING_GROUP = 'adws_shopping_group';
    const SHOPPING_GROUP_PRODUCT = 'adws_shopping_group_product';
}


================================================
FILE: src/AdwordsSectionResultSet.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

use Serps\Core\Serp\ItemPosition;
use Serps\Core\Serp\ResultDataInterface;
use Serps\Core\Serp\IndexedResultSet;

/**
 * @method AdwordsResultItem[] getItems()
 */
class AdwordsSectionResultSet extends IndexedResultSet
{

    protected $location;

    /**
     * @param string $location the locations of the results (top, bottom, right)
     */
    public function __construct($location)
    {
        $this->location = $location;
        parent::__construct(1);
    }


    /**
     * @param ResultDataInterface $item
     */
    public function addItem(ResultDataInterface $item)
    {
        $this->items[] = new AdwordsResultItem(
            $this->location,
            $item
        );
    }
}


================================================
FILE: src/Exception/GoogleCaptchaException.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Exception;

use Serps\Exception\RequestError\CaptchaException;
use Serps\SearchEngine\Google\Page\GoogleCaptcha;

/**
 * @method GoogleCaptcha getCaptcha
 */
class GoogleCaptchaException extends CaptchaException
{

    public function __construct(GoogleCaptcha $captchaResponse)
    {
        parent::__construct($captchaResponse);
    }
}


================================================
FILE: src/Exception/InvalidDOMException.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Exception;

use Serps\Exception;

class InvalidDOMException extends Exception
{

    public function __construct($message)
    {
        parent::__construct($message . ' Google DOM has possibly changed and an update may be required.');
    }
}


================================================
FILE: src/GoogleClient.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

use Serps\Core\Browser\BrowserInterface;
use Serps\Core\Cookie\ArrayCookieJar;
use Serps\Core\Cookie\CookieJarInterface;
use Serps\Core\Http\HttpClientInterface;
use Serps\Core\Http\Proxy;
use Serps\Core\UrlArchive;
use Serps\Exception;
use Serps\SearchEngine\Google\Exception\GoogleCaptchaException;
use Serps\SearchEngine\Google\Page\GoogleCaptcha;
use Serps\SearchEngine\Google\Page\GoogleError;
use Serps\SearchEngine\Google\Page\GoogleSerp;
use Serps\SearchEngine\Google\GoogleUrl;
use Serps\Exception\RequestError\PageNotFoundException;
use Serps\Exception\RequestError\RequestErrorException;
use Serps\Exception\RequestError\InvalidResponseException;

/**
 * Google client the handles google url routing, dom object constructions and request errors
 *
 */
class GoogleClient
{

    protected $defaultBrowser;

    public function __construct(BrowserInterface $browser = null)
    {
        $this->defaultBrowser = $browser;
    }

    /**
     * @param GoogleUrlInterface $googleUrl
     * @param BrowserInterface|null $browser
     * @return GoogleSerp
     * @throws Exception
     * @throws PageNotFoundException
     * @throws InvalidResponseException
     * @throws PageNotFoundException
     * @throws GoogleCaptchaException
     */
    public function query(GoogleUrlInterface $googleUrl, BrowserInterface $browser = null)
    {

        if ($googleUrl->getResultType() !== GoogleUrl::RESULT_TYPE_ALL) {
            throw new Exception(
                'The requested url is not valid for the google client.'
                . 'Google client only supports general searches. See GoogleUrl::setResultType() for more infos.'
            );
        }

        if (null === $browser) {
            $browser = $this->defaultBrowser;
        }

        if (!$browser) {
            throw new Exception('No browser given for query and no default browser was found');
        }

        $response = $browser->navigateToUrl($googleUrl);

        $statusCode = $response->getHttpResponseStatus();

        $effectiveUrl = GoogleUrlArchive::fromString($response->getEffectiveUrl()->__toString());

        if (200 == $statusCode) {
            return new GoogleSerp($response->getPageContent(), $effectiveUrl);
        } else {
            if (404 == $statusCode) {
                throw new PageNotFoundException($response);
            } else {
                $errorDom = new GoogleError($response->getPageContent(), $effectiveUrl);

                if ($errorDom->isCaptcha()) {
                    throw new GoogleCaptchaException(new GoogleCaptcha($errorDom));
                } else {
                    throw new InvalidResponseException(
                        $response,
                        sprintf(
                            "The http response from %s has an invalid status code: '%d'",
                            $response->getInitialUrl(),
                            $statusCode
                        )
                    );
                }
            }
        }
    }
}


================================================
FILE: src/GoogleUrl.php
================================================
<?php

namespace Serps\SearchEngine\Google;

use Serps\SearchEngine\Google\GoogleUrlTrait;
use Serps\Core\Url;

/**
 * A fluent builder for a google url
 */
class GoogleUrl extends Url implements GoogleUrlInterface
{
    use GoogleUrlTrait;

    const RESULT_TYPE_ALL  = 'all';
    const RESULT_TYPE_NEWS = 'nws';
    const RESULT_TYPE_VIDEOS = 'vid';
    const RESULT_TYPE_IMAGES = 'isch';
    const RESULT_TYPE_SHOPPING = 'shop';
    const RESULT_TYPE_BOOKS = 'bks';
    const RESULT_TYPE_APPS = 'app';
    const RESULT_TYPE_MAP = 'app';

    public function __construct(
        $host = 'www.google.com',
        $path = '/search',
        $scheme = 'https',
        array $query = [],
        $hash = '',
        $port = null,
        $user = null,
        $pass = null
    ) {
        parent::__construct($scheme, $host, $path, $query, $hash, $port, $user, $pass);
    }

    public static function build(
        $scheme = null,
        $host = null,
        $path = null,
        array $query = [],
        $hash = null,
        $port = null,
        $user = null,
        $pass = null
    ) {
        return new static(
            $host,
            $path,
            $scheme,
            $query,
            $hash,
            $port,
            $user,
            $pass
        );
    }

    /**
     * Set the 'lr' param for the search and auto prepend 'lang_' if not present
     * @param string $lang the lang to restrict with the format "lang_en" or "en"
     * @return $this
     */
    public function setLanguageRestriction($lang)
    {

        // lr format is lang_[ISO]
        // if $lang starts with "lang_" do nothing
        // else we prepend lang_
        if (substr($lang, 0, 5) !== 'lang_') {
            $lang = 'lang_' . $lang;
        }

        $this->setParam('lr', $lang);
        return $this;
    }

    /**
     * Set the page number of the page. Starting from 1
     * @param int $pageNumber
     * @return $this
     */
    public function setPage($pageNumber)
    {
        $pageNumber--;
        if ($pageNumber <= 0) {
            $this->removeParam('start');
        } else {
            $this->setParam('start', $pageNumber * $this->getResultsPerPage());
        }
        return $this;
    }

    /**
     * Changes the number of results per page. Between 1 and 100
     * @param int $number number of results per page
     */
    public function setResultsPerPage($number)
    {
        if ($number < 1) {
            $number = 1;
        } elseif ($number > 100) {
            // Google limits it too 100
            $number = 100;
        }

        // page backup (see below)
        $currentPage = $this->getPage();
        if ($number == 10) {
            $this->removeParam('num');
        } else {
            $this->setParam('num', $number);
        }
        // need to refresh the page because it's based on the index of the first item
        $this->setPage($currentPage);
    }

    /**
     * Set the keywords to search
     * @param $search
     * @return $this
     */
    public function setSearchTerm($search)
    {
        $this->setParam('q', $search);
        return $this;
    }

    /**
     * This allows to enable or disable google auto-correction
     * @param bool $enabled by default auto correction is enable, set it to false to disable it
     */
    public function setAutoCorrectionEnabled($enabled)
    {
        if ($enabled) {
            $this->removeParam('nfpr');
        } else {
            $this->setParam('nfpr', 1);
        }
    }

    /**
     * Sets the google result type. That's the result type in the top bar 'all', 'images', 'videos'...
     * You can use the special constant beginning with ``RESULT_TYPE_`` e.g: ``GoogleUrl::RESULT_TYPE_IMAGES``
     *
     * @param $resultType
     */
    public function setResultType($resultType)
    {
        if ($resultType == self::RESULT_TYPE_ALL) {
            $this->removeParam('tbm');
        } else {
            $this->setParam('tbm', $resultType);
        }
    }
}


================================================
FILE: src/GoogleUrlArchive.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

use Serps\Core\UrlArchive;
use Serps\SearchEngine\Google\GoogleUrl;
use Serps\SearchEngine\Google\GoogleUrlTrait;

/**
 * A frozen version of a google url
 */
class GoogleUrlArchive extends UrlArchive implements GoogleUrlInterface
{
    use GoogleUrlTrait;

    public function __construct(
        $host = 'google.com',
        $path = '/search',
        $scheme = 'https',
        array $query = [],
        $hash = '',
        $port = null,
        $user = null,
        $pass = null
    ) {
        parent::__construct($scheme, $host, $path, $query, $hash, $port, $user, $pass);
    }

    public static function build(
        $scheme = null,
        $host = null,
        $path = null,
        array $query = [],
        $hash = null,
        $port = null,
        $user = null,
        $pass = null
    ) {
        return new static(
            $host,
            $path,
            $scheme,
            $query,
            $hash,
            $port,
            $user,
            $pass
        );
    }
}


================================================
FILE: src/GoogleUrlInterface.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

use Serps\Core\Url\UrlArchiveInterface;

/**
 * The only purpose of this interface is to offer a type hinting fot GoogleUrlTrait
 * Because traits are not support as type hinting
 */
interface GoogleUrlInterface extends UrlArchiveInterface
{

    /**
     * Get the number of the page, the pages are 1 indexed
     * @return int
     */
    public function getPage();

    /**
     * @return string
     */
    public function getLanguageRestriction();

    /**
     * Get the number of results per pages
     * @return int the number of results per pages
     */
    public function getResultsPerPage();
    /**
     * Get the google result type. That's the result type in the top bar 'all', 'images', 'videos'...
     * You can use the special constant beginning with ``RESULT_TYPE_`` e.g: ``GoogleUrl::RESULT_TYPE_IMAGES``
     * @return string
     */
    public function getResultType();


    /**
     * @return GoogleUrlArchive
     */
    public function getArchive();

    /**
     * Check whether or not the auto correction of search term is enabled
     * @return bool
     */
    public function getAutoCorrectionEnabled();
}


================================================
FILE: src/GoogleUrlTrait.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

use Serps\Core\Url\QueryParam;
use Serps\SearchEngine\Google\GoogleUrl;

/**
 * Contains the base methods describing a google url.
 * @see Serps\SearchEngine\Google\GoogleUrl
 * @see Serps\SearchEngine\Google\GoogleUrlArchive
 */
trait GoogleUrlTrait
{

    abstract public function getParamValue($param, $defaultValue = null);
    abstract public function buildUrl();
    abstract public function getParamRawValue($param, $defaultValue = null);
    abstract public function getHost();
    abstract public function getPath();
    abstract public function getScheme();
    abstract public function getParams();
    abstract public function getHash();

    /**
     * Get the number of the page, the pages are 1 indexed
     * @return int
     */
    public function getPage()
    {
        $resultsPerPage = $this->getResultsPerPage();
        return 1 + $this->getParamValue('start', 0) / ($resultsPerPage > 0 ? $resultsPerPage : 10);
    }

    /**
     * @return string
     */
    public function getLanguageRestriction()
    {
        return $this->getParamValue('lr', null);
    }

    /**
     * Get the number of results per pages
     * @return int the number of results per pages
     */
    public function getResultsPerPage()
    {
        return $this->getParamValue('num', 10);
    }

    /**
     * Get the google result type. That's the result type in the top bar 'all', 'images', 'videos'...
     * You can use the special constant beginning with ``RESULT_TYPE_`` e.g: ``GoogleUrl::RESULT_TYPE_IMAGES``
     * @return string
     */
    public function getResultType()
    {
        return $this->getParamValue('tbm', GoogleUrl::RESULT_TYPE_ALL);
    }

    /**
     * Check whether or not the auto correction of search term is enabled
     * @return bool true if it enabled (it is by default)
     */
    public function getAutoCorrectionEnabled()
    {
        return 1 == $this->getParamValue('nfpr');
    }

    /**
     * Get the keywords to search
     * @return string
     */
    public function getSearchTerm()
    {
        return $this->getParamRawValue('q');
    }

    public function getArchive()
    {
        return new GoogleUrlArchive(
            $this->getHost(),
            $this->getPath(),
            $this->getScheme(),
            $this->getParams(),
            $this->getHash()
        );
    }
}


================================================
FILE: src/NaturalResultType.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google;

abstract class NaturalResultType
{

    const CLASSICAL = 'classical';
    const CLASSICAL_LARGE = 'classical_large';
    const CLASSICAL_VIDEO = 'classical_video';
    const CLASSICAL_SITELINK = 'classical_sitelink';
    const CLASSICAL_ILLUSTRATED = 'classical_illustrated';

    const KNOWLEDGE = 'knowledge';

    const PEOPLE_ALSO_ASK = 'people_also_ask';
    const PAA_QUESTION = 'paa_question';

    const IMAGE_GROUP = 'image_group';
    const IMAGE_GROUP_IMAGE = 'image_group_image';

    const VIDEO_GROUP = 'video_group';
    const VIDEO_GROUP_VIDEO = 'video_group_video';

    const IN_THE_NEWS = 'in_the_news';
    const TOP_STORIES = 'top_stories';
    const TOP_STORIES_NEWS_VERTICAL = 'top_stories_news_vertical';
    const TOP_STORIES_NEWS_CAROUSEL = 'top_stories_news_carousel';
    const TOP_STORIES_COMPOSED = 'top_stories_composed';
    const TWEETS_CAROUSEL= 'tweets_carousel';

    const MAP = 'map';
    const MAP_PLACE = 'map_place';

    const FLIGHTS = 'flights';

    const ANSWER_BOX = 'answer_box';
}


================================================
FILE: src/Page/GoogleCaptcha.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Page;

use Serps\Core\Captcha\CaptchaResponse;
use Serps\Exception;
use Serps\SearchEngine\Google\Exception\InvalidDOMException;
use Serps\SearchEngine\Google\Page\GoogleError;

class GoogleCaptcha implements CaptchaResponse
{

    /**
     * @var GoogleError
     */
    protected $googleError;

    /**
     * GoogleCaptcha constructor.
     * @param GoogleError $googleError
     */
    public function __construct(GoogleError $googleError)
    {
        $this->googleError = $googleError;
    }

    /**
     * @return GoogleError
     */
    public function getErrorPage()
    {
        return $this->googleError;
    }


    public function getCaptchaType()
    {
        return self::CAPTCHA_TYPE_RECAPTCHAV2;
    }

    /**
     * Gets the captcha image. Be aware that each call to this method will regenerate the captcha image
     * and the previous generated image will be invalid
     * @return string
     * @throws Exception
     */
    public function getData()
    {
        return null;
    }

    public function getDetectedIp()
    {

        $ipV4 = '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})';
        $ipV6 = '(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))';

        $regexp = "/($ipV4|$ipV6)/";
        $hasMatch = preg_match($regexp, $this->googleError->getDom()->C14N(), $match);
        if ($hasMatch) {
            return $match[1];
        } else {
            return null;
        }
    }
}


================================================
FILE: src/Page/GoogleDom.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Page;

use Psr\Http\Message\ResponseInterface;
use Serps\Core\Dom\WebPage;
use Serps\Core\Http\Proxy;
use Serps\Core\Http\ProxyInterface;
use Serps\Core\UrlArchive;
use Serps\SearchEngine\Google\GoogleUrl;
use Serps\SearchEngine\Google\GoogleUrlArchive;
use Serps\SearchEngine\Google\GoogleUrlInterface;

class GoogleDom extends WebPage
{

    protected $xpath;

    /**
     * @var \DOMDocument
     */
    protected $dom;

    /**
     * @var GoogleUrlInterface
     */
    protected $url;

    /**
     * store parsed json from parseJsonNode
     * @var array
     */
    private $parsedJsonStore = [];

    public function __construct($domString, GoogleUrlInterface $url)
    {
        $currentEncoding = $url->getParamValue('oe');
        if (!$currentEncoding) {
            $currentEncoding = 'UTF-8';
        }

        parent::__construct($domString, $url, $currentEncoding);
    }


    /**
     * Get a property from a google json node.
     * Google json nodes are invisible dom nodes that contain json text (found in mobile carousels for instance)
     *
     * @param string $propertyName name of the property to get
     * @param \DOMNode $node
     * @return mixed
     */
    public function getJsonNodeProperty($propertyName, \DOMNode $node)
    {
        $hash = spl_object_hash($node);

        if (!isset($this->parsedJsonStore[$hash])) {
            $nodeValue = $node->nodeValue;
            $nodeValue = trim($nodeValue, '"');

            $this->parsedJsonStore[$hash] = json_decode($nodeValue, true);
        }

        $item = $this->parsedJsonStore[$hash];

        if ($item && is_array($item) && isset($item[$propertyName])) {
            return $item[$propertyName];
        } else {
            return null;
        }
    }
}


================================================
FILE: src/Page/GoogleError.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Page;

use Serps\Core\Dom\WebPage;
use Serps\SearchEngine\Google\Page\GoogleDom;

class GoogleError extends WebPage
{

    /**
     * @return bool Check if the page is a captcha
     */
    public function isCaptcha()
    {
        return $this->cssQuery('#recaptcha')->count() > 0;
    }
}


================================================
FILE: src/Page/GoogleSerp.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Page;

use Serps\Exception;
use Serps\SearchEngine\Google\Exception\InvalidDOMException;
use Serps\SearchEngine\Google\Parser\Evaluated\AdwordsParser;
use Serps\SearchEngine\Google\Parser\Evaluated\MobileNaturalParser;
use Serps\SearchEngine\Google\Parser\Evaluated\NaturalParser;
use Serps\SearchEngine\Google\Parser\Evaluated\MobileAdwordsParser;
use Serps\Stubs\RelatedSearch;

class GoogleSerp extends GoogleDom
{

    /**
     * Get the location detected by google
     * @return string
     */
    public function getLocation()
    {
        $locationRegexp = '#"uul_text":"([^"]+)"#';

        preg_match($locationRegexp, $this->dom->C14N(), $matches);

        if ($matches && isset($matches[1])) {
            return $matches[1];
        }
        return null;
    }

    /**
     * @return \Serps\Core\Serp\IndexedResultSet
     */
    public function getNaturalResults()
    {
        if ($this->javascriptIsEvaluated()) {
            if ($this->isMobile()) {
                $parser = new MobileNaturalParser();
            } else {
                $parser = new NaturalParser();
            }
        } else {
            throw new InvalidDOMException('Raw dom is not supported, please provide an evaluated version of the dom');
        }
        return $parser->parse($this);
    }


    /**
     * @return \Serps\Core\Serp\CompositeResultSet
     * @throws Exception
     * @throws InvalidDOMException
     */
    public function getAdwordsResults()
    {
        if ($this->javascriptIsEvaluated()) {
            if ($this->isMobile()) {
                $parser = new MobileAdwordsParser();
            } else {
                $parser = new AdwordsParser();
            }

            return $parser->parse($this);
        } else {
            throw new InvalidDOMException('Raw dom is not supported, please provide an evaluated version of the dom');
        }
    }

    /**
     * Get the total number of results available for the search terms
     * @return int the number of results
     * @throws InvalidDOMException
     */
    public function getNumberOfResults()
    {
        $item = $this->cssQuery('#resultStats');
        if ($item->length != 1) {
            return null;
        }

        // number of results is followed by time, we want to targets the first node (text node) that is the number of
        // results
        $nodeValue = $item->getNodeAt(0)->getChildren()->getNodeAt(0)->getNodeValue();

        if (!$nodeValue) {
            return null;
        }


        // WARNING: The number of result is explained in different format according to the country. Fon instance:
        // UK:  6,200,000
        // FR:  6 200 000
        // DE:  2.200.000
        // IN:  62,00,000
        // We have to use a global matcher
        $matched = preg_match_all('/([0-9]+[^0-9]?)+/u', $nodeValue, $countMatch);

        if (!$matched) {
            return null;
        }

        // get the last count, when we use pagination the first count is the page number
        // see https://github.com/serp-spider/search-engine-google/issues/100
        $count = $countMatch[0][count($countMatch[0]) - 1];

        return (int) preg_replace('/[^0-9]/', '', $count);
    }

    public function javascriptIsEvaluated()
    {
        $body = $this->getXpath()->query('//body');

        if ($body->length != 1) {
            throw new Exception('No body found');
        }

        $body = $body->item(0);
        /** @var $body \DOMElement */
        $class = $body->getAttribute('class');
        if ($class=='hsrp') {
            return false;
        } elseif (strstr($class, 'srp')) {
            return true;
        } else {
            throw new InvalidDOMException('Unable to check javascript status.');
        }
    }

    /**
     * @return array|RelatedSearch[]
     */
    public function getRelatedSearches()
    {
        $relatedSearches = [];
        if ($this->isMobile()) {
            $items = $this->cssQuery('#botstuff div:not(#bres) a.QsZ7bb');

            if ($items->length == 0) {
                // TODO BC version to remove
                $items = $this->cssQuery('#botstuff div:not(#bres)>._Qot>div>a');
            }

            if ($items->length > 0) {
                foreach ($items as $item) {
                    /* @var $item \DOMElement */
                    $result = new \stdClass();
                    $result->title = $item->childNodes->item(0)->nodeValue;
                    $result->url = $this->getUrl()->resolveAsString($item->getAttribute('href'));
                    $relatedSearches[] = $result;
                }
            }
        } else {
            $items = $this->cssQuery('#brs ._e4b>a, #brs .card-section a'); // TODO ._ed4 is outdated
            if ($items->length > 0) {
                foreach ($items as $item) {
                    /* @var $item \DOMElement */
                    $result = new \stdClass();
                    $result->title = $item->nodeValue;
                    $result->url = $this->getUrl()->resolveAsString($item->getAttribute('href'));
                    $relatedSearches[] = $result;
                }
            }
        }


        return $relatedSearches;
    }

    public function isMobile()
    {
        $item = $this->cssQuery('head meta[name="viewport"]');
        return $item->length == 1;
    }
}


================================================
FILE: src/Page/NotFound.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Page;

class NotFound extends GoogleDom
{

}


================================================
FILE: src/Parser/AbstractAdwordsParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser;

use Serps\Core\Serp\CompositeResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;

abstract class AbstractAdwordsParser implements ParserInterface
{

    /**
     * @var ParserInterface[]
     */
    private $parsers = null;

    /**
     * Generate a list of parsers to be used when parsing dom
     * @return ParserInterface[]
     */
    abstract public function generateParsers();

    /**
     * @return ParserInterface[]
     */
    public function getParsers()
    {
        if (null == $this->parsers) {
            $this->parsers = $this->generateParsers();
        }
        return $this->parsers;
    }

    /**
     * @inheritdoc
     */
    public function parse(GoogleDom $googleDom)
    {
        $resultsSets = new CompositeResultSet();

        $parsers = $this->getParsers();

        foreach ($parsers as $parser) {
            $resultsSets->addResultSet(
                $parser->parse($googleDom)
            );
        }

        return $resultsSets;
    }
}


================================================
FILE: src/Parser/AbstractParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser;

use Serps\Core\Dom\DomNodeList;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;

abstract class AbstractParser implements ParserInterface
{

    /**
     * @var ParsingRuleInterface[]
     */
    private $rules = null;

    /**
     * @return ParsingRuleInterface[]
     */
    abstract protected function generateRules();

    /**
     * @param GoogleDom $googleDom
     * @return DomNodeList
     */
    abstract protected function getParsableItems(GoogleDom $googleDom);


    /**
     * @return ParsingRuleInterface[]
     */
    public function getRules()
    {
        if (null == $this->rules) {
            $this->rules = $this->generateRules();
        }
        return $this->rules;
    }

    /**
     * Parses the given google dom
     * @param GoogleDom $googleDom
     * @return IndexedResultSet
     */
    public function parse(GoogleDom $googleDom)
    {
        $elementGroups = $this->getParsableItems($googleDom);
        $resultSet = $this->createResultSet($googleDom);
        return $this->parseGroups($elementGroups, $resultSet, $googleDom);
    }

    /**
     * Defines what resultset to use for results
     * @param GoogleDom $googleDom
     * @return IndexedResultSet
     */
    protected function createResultSet(GoogleDom $googleDom)
    {
        $startingAt = (int) $googleDom->getUrl()->getParamValue('start', 0);
        return new IndexedResultSet($startingAt + 1);
    }

    /**
     * @param $elementGroups
     * @param IndexedResultSet $resultSet
     * @param $googleDom
     * @return IndexedResultSet
     */
    protected function parseGroups(DomNodeList $elementGroups, IndexedResultSet $resultSet, $googleDom)
    {
        $rules = $this->getRules();

        foreach ($elementGroups as $group) {
            if (!($group instanceof \DOMElement)) {
                continue;
            }
            foreach ($rules as $rule) {
                $match = $rule->match($googleDom, $group);
                if ($match instanceof \DOMNodeList) {
                    $this->parseGroups(new DomNodeList($match, $googleDom), $resultSet, $googleDom);
                    break;
                } elseif ($match instanceof DomNodeList) {
                    $this->parseGroups($match, $resultSet, $googleDom);
                    break;
                } else {
                    switch ($match) {
                        case ParsingRuleInterface::RULE_MATCH_MATCHED:
                            $rule->parse($googleDom, $group, $resultSet);
                            break 2;
                        case ParsingRuleInterface::RULE_MATCH_STOP:
                            break 2;
                    }
                }
            }
        }
        return $resultSet;
    }
}


================================================
FILE: src/Parser/Evaluated/AdwordsParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated;

use Serps\SearchEngine\Google\AdwordsResultType;
use Serps\Core\Dom\Css;
use Serps\SearchEngine\Google\Parser\AbstractAdwordsParser;

class AdwordsParser extends AbstractAdwordsParser
{

    /**
     * @inheritdoc
     */
    public function generateParsers()
    {
        return [
            // Adwords top
            new AdwordsSectionParser(
                Css::toXPath('div#tads li.ads-ad, div#tvcap ._oc'),
                AdwordsResultType::SECTION_TOP
            ),

            // Adwords bottom
            new AdwordsSectionParser(
                "descendant::div[@id = 'bottomads']//li[@class='ads-ad']",
                AdwordsResultType::SECTION_BOTTOM
            )
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/AdwordsSectionParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated;

use Serps\SearchEngine\Google\AdwordsSectionResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\AbstractParser;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Adwords\AdwordsItem;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Adwords\Shopping;

/**
 * Parses adwords results from a google SERP
 */
class AdwordsSectionParser extends AbstractParser
{

    protected $pathToItems;
    protected $location;

    /**
     * @param $pathToItems
     */
    public function __construct($pathToItems, $location)
    {
        $this->pathToItems = $pathToItems;
        $this->location = $location;
    }

    /**
     * @inheritdoc
     */
    protected function createResultSet(GoogleDom $googleDom)
    {
        return new AdwordsSectionResultSet($this->location);
    }

    /**
     * @inheritdoc
     */
    protected function generateRules()
    {
        return [
            new AdwordsItem(),
            new Shopping()
        ];
    }

    /**
     * @inheritdoc
     */
    protected function getParsableItems(GoogleDom $googleDom)
    {
        return $googleDom->xpathQuery($this->pathToItems);
    }
}


================================================
FILE: src/Parser/Evaluated/MobileAdwordsParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated;

use Serps\SearchEngine\Google\AdwordsResultType;
use Serps\Core\Dom\Css;
use Serps\SearchEngine\Google\Parser\AbstractAdwordsParser;

class MobileAdwordsParser extends AbstractAdwordsParser
{

    /**
     * @inheritdoc
     */
    public function generateParsers()
    {
        return [
            // Adwords top
            new MobileAdwordsSectionParser(
                Css::toXPath('#tads li.ads-fr, #tvcap'),
                AdwordsResultType::SECTION_TOP
            ),

            // Adwords bottom
            new MobileAdwordsSectionParser(
                Css::toXPath('#tadsb li.ads-fr'),
                AdwordsResultType::SECTION_BOTTOM
            )
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/MobileAdwordsSectionParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated;

use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Adwords\AdwordsItemMobile;

/**
 * Parses adwords results from a google SERP
 */
class MobileAdwordsSectionParser extends AdwordsSectionParser
{
    /**
     * @inheritdoc
     */
    protected function generateRules()
    {
        return [
            new AdwordsItemMobile()
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/MobileNaturalParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated;

use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\AbstractParser;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalCardsResultO9g5cc;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalCardsResultZ1m;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalCardsResultZINbbc;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalCardsVideoResult;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\ComposedTopStories;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Divider;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\ImageGroup;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\ImageGroupCarousel;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\LargeClassicalResult;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\KnowledgeCard;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalCardsResult;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\MapMobile;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\PeopleAlsoAsk;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\SearchResultGroup;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\TweetsCarouselZ1m;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\VideoGroup;

/**
 * Parses natural results from a mobile google SERP
 */
class MobileNaturalParser extends AbstractParser
{

    /**
     * @inheritdoc
     */
    protected function generateRules()
    {
        return [
            new Divider(),
            new SearchResultGroup(),
            new ClassicalCardsResultO9g5cc(),
            new ClassicalCardsResultZINbbc(), // TODO maybe outdated
            new ClassicalCardsResultZ1m(), // TODO remove (outdated)
            new ClassicalCardsVideoResult(),
            new ClassicalCardsResult(),
            new MapMobile(),
            new TweetsCarouselZ1m(), // TODO replace and remove (outdated)
            new ImageGroupCarousel(),
            new ComposedTopStories(),
            new VideoGroup(),
            new ImageGroup(),
            new PeopleAlsoAsk(), // people also ask must be placed before knowledge card to stop parsing
            new KnowledgeCard()
        ];
    }

    /**
     * @inheritdoc
     */
    protected function getParsableItems(GoogleDom $googleDom)
    {
        $xpathObject = $googleDom->getXpath();
        $xpathElementGroups = "//div[@id = 'ires']/*[@id = 'rso']/*";
        return $xpathObject->query($xpathElementGroups);
    }
}


================================================
FILE: src/Parser/Evaluated/NaturalParser.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated;

use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\AbstractParser;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\AnswerBox;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalResult;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Divider;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Flight;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\ImageGroup;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\InTheNews;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Map;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalCardsResult;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\MapLegacy;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\PeopleAlsoAsk;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\SearchResultGroup;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\TopStoriesCarousel;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\TopStoriesVertical;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\TweetsCarousel;
use Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical\ClassicalWithLargeVideo;

/**
 * Parses natural results from a google SERP
 */
class NaturalParser extends AbstractParser
{

    /**
     * @inheritdoc
     */
    protected function generateRules()
    {
        return [
            new Divider(),
            new SearchResultGroup(),
            new ClassicalResult(),
            new ClassicalCardsResult(),
            new ImageGroup(),
            new TopStoriesCarousel(),
            new TopStoriesVertical(),
            new TweetsCarousel(),
            new ClassicalWithLargeVideo(),
            new InTheNews(),
            new Map(),
            new MapLegacy(),
            new AnswerBox(),
            new Flight(),
            new PeopleAlsoAsk()
        ];
    }

    /**
     * @inheritdoc
     */
    protected function getParsableItems(GoogleDom $googleDom)
    {
        return $googleDom->xpathQuery("//*[@id = 'rso']/*");
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Adwords/AdwordsItem.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Adwords;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\AdwordsResultType;
use Serps\Core\Dom\Css;
use Serps\SearchEngine\Google\Exception\InvalidDOMException;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class AdwordsItem implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($node->getAttribute('class') == 'ads-ad') {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }
    public function parse(GoogleDom $googleDOM, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = [
            'title' => function () use ($googleDOM, $node) {
                $aTag = $googleDOM->getXpath()->query('descendant::h3/a[2]', $node)->item(0);
                if (!$aTag) {
                    $aTag = $googleDOM->getXpath()->query('descendant::h3', $node)->item(0);
                    if (!$aTag) {
                        return null;
                    }
                }
                return $aTag->nodeValue;
            },
            'url' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->getXpath()->query('descendant::h3/a[2]', $node)->item(0); // TODO remove
                if (!$aTag) {
                    $aTag = $googleDOM->cssQuery('a', $node)->item(0);
                    if (!$aTag) {
                        throw new InvalidDOMException('Cannot find ads anchor');
                    }
                }

                return $googleDOM->getUrl()->resolveAsString($aTag->getAttribute('href'));
            },
            'visurl' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->getXpath()->query(
                    Css::toXPath('div.ads-visurl>cite'),
                    $node
                )->item(0);

                if (!$aTag) {
                    return null;
                }
                return $aTag->nodeValue;
            },
            'description' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->getXpath()->query(
                    Css::toXPath('div.ads-creative'),
                    $node
                )->item(0);

                if (!$aTag) {
                    return null;
                }
                return $aTag->nodeValue;
            },
        ];

        $resultSet->addItem(new BaseResult(AdwordsResultType::AD, $item));
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Adwords/AdwordsItemMobile.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Adwords;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\AdwordsResultType;
use Serps\SearchEngine\Google\Exception\InvalidDOMException;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class AdwordsItemMobile implements ParsingRuleInterface
{

    /**
     * @inheritdoc
     */
    public function match(GoogleDom $dom, DomElement $node)
    {
        if ($node->hasClass('ads-fr')) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    /**
     * @inheritdoc
     */
    public function parse(GoogleDom $googleDOM, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = [
            'title' => function () use ($googleDOM, $node) {
                $aTag = $googleDOM->cssQuery('a .MUxGbd.v0nnCb', $node)->item(0);
                if (!$aTag) {
                    throw new InvalidDOMException('Cannot find title for mobile adwords.');
                }
                return $aTag->nodeValue;
            },
            'url' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->cssQuery('a', $node)->item(0);
                if (!$aTag) {
                    throw new InvalidDOMException('Cannot find ads anchor');
                }
                return $googleDOM->getUrl()->resolveAsString($aTag->getAttribute('href'));
            },
            'visurl' => function () use ($node, $googleDOM) {
                return $googleDOM->cssQuery('.qzEoUe', $node)->getNodeAt(0)->getNodeValue();
            },
            'description' => function () use ($node, $googleDOM) {
                return $googleDOM->cssQuery('div.BmP5tf>div.MUxGbd', $node)
                    ->getNodeAt(0)
                    ->getNodeValue();
            },
        ];

        $resultSet->addItem(new BaseResult(AdwordsResultType::AD, $item));
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Adwords/Shopping.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Adwords;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\AdwordsResultType;
use Serps\Core\Dom\Css;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class Shopping implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        $class = $node->getAttribute('class');

        if (strpos(' ' . $class . ' ', ' _oc ')) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }
    public function parse(GoogleDom $googleDOM, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = [
            'products' => function () use ($googleDOM, $node) {
                $items = [];
                $xpathCards = Css::toXPath('.pla-unit');
                $productNodes = $googleDOM->getXpath()->query($xpathCards, $node);
                foreach ($productNodes as $productNode) {
                    $items[] = $this->parseItem($googleDOM, $productNode);
                }
                return $items;
            }
        ];


        $resultSet->addItem(new BaseResult(AdwordsResultType::SHOPPING_GROUP, $item));
    }

    public function parseItem(GoogleDom $googleDOM, \DOMNode $node)
    {

        return new BaseResult(AdwordsResultType::SHOPPING_GROUP_PRODUCT, [
            'title' => function () use ($googleDOM, $node) {
                $aTag = $googleDOM->getXpath()->query(Css::toXPath('.pla-unit-title-link'), $node)->item(0);
                if (!$aTag) {
                    return null;
                }
                return $aTag->nodeValue;
            },
            'url' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->getXpath()->query(Css::toXPath('.pla-unit-title-link'), $node)->item(0);
                if (!$aTag) {
                    return $googleDOM->getUrl()->resolve('/');
                }
                return $googleDOM->getUrl()->resolveAsString($aTag->getAttribute('href'));
            },
            'image' => function () use ($node, $googleDOM) {
                $imgTag = $googleDOM->getXpath()->query(
                    Css::toXPath('.pla-unit-img-container-link img'),
                    $node
                )->item(0);

                if (!$imgTag) {
                    return null;
                }
                return $imgTag->getAttribute('src');
            },
            'target' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->getXpath()->query(
                    Css::toXPath('div._mC span.a'),
                    $node
                )->item(0);

                if (!$aTag) {
                    return null;
                }
                return $aTag->nodeValue;
            },
            'price' => function () use ($node, $googleDOM) {
                $priceTag = $googleDOM->getXpath()->query(
                    Css::toXPath('._QD._pvi'),
                    $node
                )->item(0);

                if (!$priceTag) {
                    return null;
                }
                return $priceTag->nodeValue;
            }
        ]);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/AnswerBox.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class AnswerBox implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($node->getAttribute('class') == 'g mnr-c g-blk'
            && (
                $dom->cssQuery('.ifM9O', $node)->length == 1 ||
                $dom->cssQuery('._Z7', $node)->length == 1      // TODO used for BC, remove in the future
            )
        ) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    protected function parseNode(GoogleDom $dom, \DOMElement $node)
    {
        return [
            'title'   => function () use ($dom, $node) {
                $aTag = $dom->cssQuery('.rc .r a', $node)
                    ->item(0);
                if (!$aTag) {
                    // TODO ERROR
                    return;
                }

                if ($h3Tag = $dom->cssQuery('h3', $aTag)->item(0)) {
                    return $h3Tag->getNodeValue();
                }

                return $aTag->nodeValue;
            },
            'url'     => function () use ($dom, $node) {
                $aTag = $dom->cssQuery('.rc .r a', $node)
                    ->item(0);
                if (!$aTag) {
                    // TODO ERROR
                    return;
                }
                return $dom->getUrl()->resolveAsString($aTag->getAttribute('href'));
            },
            'destination' => function () use ($dom, $node) {
                $citeTag = $dom->cssQuery('.rc .s cite', $node)
                    ->item(0);
                if (!$citeTag) {
                    // TODO ERROR
                    return;
                }
                return $citeTag->nodeValue;
            },
            'description' => function () use ($dom, $node) {
                // TODO "mod ._Tgc" kept for BC, remove in the future
                $citeTag = $dom->cssQuery('.mod ._Tgc, .mod .Y0NH2b', $node)
                    ->item(0);
                if (!$citeTag) {
                    // TODO ERROR
                    return;
                }
                return $citeTag->nodeValue;
            },
        ];
    }

    public function parse(GoogleDom $dom, \DOMElement $node, IndexedResultSet $resultSet)
    {
        $item = new BaseResult(
            [NaturalResultType::ANSWER_BOX],
            $this->parseNode($dom, $node)
        );
        $resultSet->addItem($item);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResult.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;

class ClassicalCardsResult extends ClassicalResult
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        if ($node->hasClass('mnr-c')) {
            $hasgblk = $node->hasClass('g-blk');
            // class g-blk is common to classical large, answer box, and some other cards results,
            // but not present on base classical results
            // class ._Hi is unique to large classical results

            if ((
                    !$hasgblk ||
                    ($hasgblk && $dom->cssQuery('._Hi', $node)->length == 1)
                ) &&
                $dom->cssQuery('.rc', $node)->length == 1
            ) {
                return self::RULE_MATCH_MATCHED;
            }
        }
        return self::RULE_MATCH_NOMATCH;
    }


    /**
     * Is large dectection is not the same for cards and non cards classical results
     * @param GoogleDom $dom
     * @param \DomElement $node
     * @return bool
     */
    protected function isLarge(GoogleDom $dom, \DomElement $node)
    {
        return $dom->cssQuery('._Hi', $node)->length == 1;
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultO9g5cc.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Dom\DomElement;
use Serps\Core\Dom\DomNodeInterface;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

/**
 * First seen in august 2018 in mobile pages, replacing .ZINbbc
 */
class ClassicalCardsResultO9g5cc implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        $res = $dom->cssQuery('.O9g5cc.xpd a.C8nzq', $node);
        // TODO consider removing .ZINbbc (replaced with .O9g5cc in august 2018)

        if ($res->length == 1) {
            return self::RULE_MATCH_MATCHED;
        }

        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $classicalData = $this->parseNode($dom, $node);

        $resultTypes = [NaturalResultType::CLASSICAL];

        $item = new BaseResult($resultTypes, $classicalData);
        $resultSet->addItem($item);
    }

    protected function parseNode(GoogleDom $dom, DomNodeInterface $node)
    {
        return [
            'title' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('a .MUxGbd', $node)
                    ->getNodeAt(0)
                    ->getNodeValue();
            },
            'isAmp' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('.ZseVEf', $node)
                    ->length > 0;
            },
            'url' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('a.C8nzq', $node)
                    ->getNodeAt(0)
                    ->getAttribute('href');
            },
            'destination' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('span.QHTnWc, span.qzEoUe', $node) // TODO ".QHTnWc" appears to be outdated
                    ->getNodeAt(0)
                    ->getNodeValue();
            },
            'description' => function () use ($dom, $node) {
                // TODO remove BC with ".JTuIPc:not(a)>.MUxGbd"
                return $dom
                    ->cssQuery('.JTuIPc:not(a)>.MUxGbd, div.BmP5tf>div.MUxGbd, div.LZ8hH>div.MUxGbd', $node)
                    ->getNodeAt(0)
                    ->getNodeValue();
            }
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultZ1m.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

/**
 * Special version of classical card that appeared end 2017 in mobile serps.
 * Those results are identified by the class ._Z1m
 *
 * Z1m card results have nothing in common with other results so they have to be parsed independently
 */
class ClassicalCardsResultZ1m implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        if ($node->childNodes->length == 1) {
            $childNode = $node->getChildren()->getNodeAt(0);

            // TODO _Z1m results appear to be outdated
            // check if has class _Z1m
            if ($childNode->hasClass('_Z1m')) {
                // check _a5r
                if ($node->childNodes->item(0)->childNodes->length == 1) {
                    /** @var DomElement $subChildNode */
                    $subChildNode = $childNode->childNodes->item(0);

                    if ($subChildNode->hasClass('_a5r')) {
                        return self::RULE_MATCH_MATCHED;
                    }
                }

                // check a._Olt._bCp
                if ($dom->cssQuery('a._Olt._bCp', $node)->length > 0) {
                    return self::RULE_MATCH_MATCHED;
                }
            }
        }

        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $data = $this->parseNode($dom, $node->childNodes->item(0));

        $resultTypes = [NaturalResultType::CLASSICAL];

        $item = new BaseResult($resultTypes, $data);
        $resultSet->addItem($item);
    }

    protected function parseNode(GoogleDom $dom, DomElement $node)
    {
        return [
            'title' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('._ees', $node)
                    ->item(0)
                    ->nodeValue;
            },
            'isAmp' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('.amp_r', $node)
                    ->length > 0;
            },
            'url' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('a._Olt', $node)
                    ->item(0)
                    ->getAttribute('href');
            },
            'destination' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('span._Clt', $node)
                    ->item(0)
                    ->nodeValue;
            },
            'description' => function () use ($dom, $node) {
                $res = $dom
                    ->cssQuery('div>div._bCp>div._H1m', $node);

                if ($res->length > 0) {
                    return $res->item(0)->getNodeValue();
                } else {
                    return null;
                }
            }
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultZINbbc.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Dom\DomElement;
use Serps\Core\Dom\DomNodeInterface;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

/**
 * First seen in 2018 in mobile pages
 * TODO consider removing .ZINbbc (replaced with .O9g5cc in august 2018)
 */
class ClassicalCardsResultZINbbc implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        $res = $dom->cssQuery('.ZINbbc.xpd a.C8nzq', $node);

        if ($res->length == 1) {
            return self::RULE_MATCH_MATCHED;
        }

        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $classicalData = $this->parseNode($dom, $node);

        $resultTypes = [NaturalResultType::CLASSICAL];

        $item = new BaseResult($resultTypes, $classicalData);
        $resultSet->addItem($item);
    }

    protected function parseNode(GoogleDom $dom, DomNodeInterface $node)
    {
        return [
            'title' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('a .pIpgAc', $node)
                    ->getNodeAt(0)
                    ->getNodeValue();
            },
            'isAmp' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('.ZseVEf', $node)
                    ->length > 0;
            },
            'url' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('a.C8nzq', $node)
                    ->getNodeAt(0)
                    ->getAttribute('href');
            },
            'destination' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('span.QHTnWc, span.qzEoUe', $node) // TODO ".QHTnWc" appears to be outdated
                    ->getNodeAt(0)
                    ->getNodeValue();
            },
            'description' => function () use ($dom, $node) {
                $res = $dom
                    ->cssQuery('.JTuIPc', $node);

                if ($res->length > 1) {
                    return $dom->cssQuery('.pIpgAc', $res->getNodeAt(1))->getNodeAt(0)->getNodeValue();
                } else {
                    return null;
                }
            }
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsVideoResult.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Dom\DomElement;
use Serps\Core\Media\MediaFactory;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;

class ClassicalCardsVideoResult extends ClassicalCardsResult
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        $match = parent::match($dom, $node);
        if ($match === self::RULE_MATCH_MATCHED) {
            if ($dom->cssQuery('.A995L', $node)->length) {
                return self::RULE_MATCH_MATCHED;
            }
            return self::RULE_MATCH_NOMATCH;
        } else {
            return $match;
        }
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $resultTypes = [NaturalResultType::CLASSICAL, NaturalResultType::CLASSICAL_VIDEO];

        $item = new BaseResult($resultTypes, [
            'title' => function () use ($dom, $node) {
                return $dom->cssQuery('h3 a', $node)->getNodeAt(0)->getNodeValue();
            },
            'url' => function () use ($dom, $node) {
                return $dom->getUrl()->resolveAsString(
                    $dom->cssQuery('h3 a', $node)->getNodeAt(0)->getAttribute('href')
                );
            },
            'destination' => function () use ($dom, $node) {
                return $dom->cssQuery('.RXIhdf', $node)->getNodeAt(0)->getNodeValue();
            },
            'description' => '',
            'isAmp' => false,
            'videoLarge' => false,
            'videoCover' => function () use ($dom, $node) {
                return MediaFactory::createMediaFromSrc(
                    $dom->cssQuery('.A995L a img', $node)->getNodeAt(0)->getAttribute('src')
                );
            }
        ]);
        $resultSet->addItem($item);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalResult.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Dom\DomElement;
use Serps\Core\Media\MediaFactory;
use Serps\SearchEngine\Google\Exception\InvalidDOMException;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class ClassicalResult implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        if ($node->getAttribute('class') == 'g') {
            if ($dom->cssQuery('.rc', $node)->length == 1) {
                return self::RULE_MATCH_MATCHED;
            }
        }
        return self::RULE_MATCH_NOMATCH;
    }

    protected function parseNode(GoogleDom $dom, \DomElement $node)
    {

        // find the title/url
        /* @var $aTag \DOMElement */
        $aTag = $dom
            ->xpathQuery("descendant::*[(self::div or self::h3) and @class='r'][1]/a", $node)
            ->item(0);
        if (!$aTag) {
            throw new InvalidDOMException('Cannot parse a classical result.');
        }

        /* @var $h3Tag \DOMElement */
        $h3Tag = $dom
            ->xpathQuery('descendant::h3', $node)
            ->item(0);
        if (!$h3Tag) {
            throw new InvalidDOMException('Cannot parse a classical result.');
        }

        $destinationTag = $dom
            ->cssQuery('div.f cite, div.TbwUpd cite', $node)
            ->getNodeAt(0);

        if (is_a($destinationTag, Serps\Core\Dom\NullDomNode::class)) {
            throw new InvalidDOMException('Cannot parse a classical result.');
        }

        $descriptionTag = $dom
            ->xpathQuery("descendant::span[@class='st']", $node)
            ->item(0);

        return [
            'title'   => $h3Tag->nodeValue,
            'url'     => $dom->getUrl()->resolveAsString($aTag->getAttribute('href')),
            'destination' => $destinationTag->getNodeValue(),
            // trim needed for mobile results coming with an initial space
            'description' => $descriptionTag ? trim($descriptionTag->nodeValue) : null,
            'isAmp' => function () use ($dom, $node) {
                return $dom
                        ->cssQuery('.amp_r', $node)
                        ->length > 0;
            },
        ];
    }

    /**
     * If isLarge() matched, this will parse the content of site links
     * @param GoogleDom $dom
     * @param \DomElement $node
     * @return \Closure
     */
    protected function parseSiteLink(GoogleDom $dom, \DomElement $node)
    {
        return function () use ($dom, $node) {
            $items = $dom->cssQuery('.mslg .sld', $node);
            $siteLinksData = [];
            foreach ($items as $item) {
                $siteLinksData[] = new BaseResult(NaturalResultType::CLASSICAL_SITELINK, [
                    'title' => function () use ($dom, $item) {
                        return $dom->cssQuery('h3.r a', $item)
                            ->getNodeAt(0)
                            ->getNodeValue();
                    },
                    'description' => function () use ($dom, $item) {
                        return $dom->cssQuery('.st', $item)
                            ->getNodeAt(0)
                            ->getNodeValue();
                    },
                    'url' => function () use ($dom, $item) {
                        return $dom->cssQuery('h3.r a', $item)
                            ->getNodeAt(0)
                            ->getAttribute('href');
                    },
                ]);
            }
            return $siteLinksData;
        };
    }

    /**
     * Check if has site links. Might be overriden by subparser like ClassicalCard
     * @param GoogleDom $dom
     * @param \DomElement $node
     * @return bool
     */
    protected function isLarge(GoogleDom $dom, \DomElement $node)
    {
        return $dom->cssQuery('.nrgt', $node)->length == 1;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $data = $this->parseNode($dom, $node);

        $resultTypes = [NaturalResultType::CLASSICAL];


        // CLASSICAL RESULT MIGHT BE ENLARGED WITH SITELINKS
        if ($this->isLarge($dom, $node)) {
            $data['sitelinks'] = $this->parseSiteLink($dom, $node);
            $resultTypes[] = NaturalResultType::CLASSICAL_LARGE;
        }


        // classical result can have a video thumbnail
        $thumb = $dom->getXpath()
            ->query("descendant::g-img[@class='_ygd']/img", $node)
            ->item(0);

        if ($thumb) {
            $resultTypes[] = NaturalResultType::CLASSICAL_ILLUSTRATED;

            $data['thumb'] = function () use ($thumb) {
                if ($thumb) {
                    return MediaFactory::createMediaFromSrc($thumb->getAttribute('src'));
                } else {
                    return null;
                }
            };
        }

        $videoDuration = $dom->cssQuery('.vdur', $node);
        if ($videoDuration->length == 1) {
            $resultTypes[] = NaturalResultType::CLASSICAL_VIDEO;
            $data['videoLarge'] = false;
        }


        $item = new BaseResult($resultTypes, $data);
        $resultSet->addItem($item);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalWithLargeVideo.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural\Classical;

use Serps\Core\Media\MediaFactory;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class ClassicalWithLargeVideo implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($node->getAttribute('class') == 'g mnr-c g-blk'
            && $dom->cssQuery('.knowledge-block__video-nav-block', $node)->length == 1
        ) {
            return self::RULE_MATCH_MATCHED;
        } else {
            return self::RULE_MATCH_NOMATCH;
        }
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $xpath = $dom->getXpath();
        $aTag = $xpath->query("descendant::h3[@class='r'][1]/a", $node)->item(0);

        if (!$aTag) {
            return false;
        }

        $destinationTag = $xpath
            ->query("descendant::div[@class='f kv _SWb']/cite", $node)
            ->item(0);

        $data = [
            'title'   => $aTag->nodeValue,
            'url'     => $dom->getUrl()->resolveAsString($aTag->getAttribute('href')),
            'destination' => $destinationTag ? $destinationTag->nodeValue : null,
            'description' => null,
            'videoLarge'  => true,
            'thumb' => null,
            'videoCover'  => function () use ($dom, $node) {
                $imageTag = $dom
                    ->cssQuery('._ELb img', $node)
                    ->item(0);
                if ($imageTag) {
                    return MediaFactory::createMediaFromSrc($imageTag->getAttribute('src')); // TODO 1p gif ?
                } else {
                    return null;
                }
            }
        ];

        $resultSet->addItem(new BaseResult([NaturalResultType::CLASSICAL_VIDEO, NaturalResultType::CLASSICAL], $data));
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/ComposedTopStories.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\Serp\ResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class ComposedTopStories implements ParsingRuleInterface
{
    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('._Fzo ._HSj', $node)->length == 1
            && $dom->cssQuery('.dbsr', $node)->length > 0
            // Dont use _yyh, _JTg or _bfj class because it's common to all carousel
        ) {
            return self::RULE_MATCH_MATCHED;
        }


        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = new BaseResult(
            [NaturalResultType::TOP_STORIES, NaturalResultType::TOP_STORIES_COMPOSED],
            $this->parseNode($dom, $node)
        );
        $resultSet->addItem($item);
    }

    private function parseNode(GoogleDom $dom, $node)
    {
        return [

            'isCarousel' => true,
            'isVertical' => true,

            'news' => function () use ($dom, $node) {
                $news =  $this->parseVerticalResults($dom, $node);
                $news = array_merge($news, $this->parseCarouselResults($dom, $node));

                $resultSet = new ResultSet();
                $resultSet->addItems($news);

                return $resultSet;
            },
        ];
    }

    private function parseVerticalResults(GoogleDom $dom, DomElement $node)
    {
        $news = [];
        $nodes = $dom->cssQuery('.dbsr', $node);

        foreach ($nodes as $newsNode) {
            $news[] = new BaseResult(NaturalResultType::TOP_STORIES_NEWS_VERTICAL, [
                'title' => function () use ($dom, $newsNode) {
                    $el = $dom->cssQuery('._eNq>span', $newsNode)->item(0);
                    return $el->nodeValue;
                },
                'url' => function () use ($dom, $newsNode) {
                    $el = $dom->cssQuery('._rNq>a', $newsNode)->item(0);
                    return $el->getAttribute('href');
                }
            ]);
        }

        return $news;
    }

    private function parseCarouselResults(GoogleDom $dom, DomElement $node)
    {
        $news = [];
        $nodes = $dom->cssQuery('._HSj ._ERj', $node);

        foreach ($nodes as $newsNode) {
            $news[] = new BaseResult(NaturalResultType::TOP_STORIES_NEWS_CAROUSEL, [
                'title' => function () use ($dom, $newsNode) {
                    $el = $dom->cssQuery('._IRj', $newsNode)->item(0);
                    return $el->nodeValue;
                },
                'url' => function () use ($dom, $newsNode) {
                    $el = $dom->cssQuery('g-inner-card._KBh>a', $newsNode)->item(0);
                    return $el->getAttribute('href');
                }
            ]);
        }

        return $news;
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Divider.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class Divider implements \Serps\SearchEngine\Google\Parser\ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        /**
         * Divider should not be parsed and for performance we just skip the parsing
         */
        if ('hr' == $node->tagName || 'rgsep' == $node->getAttribute('class')) {
            return self::RULE_MATCH_STOP;
        }
    }

    public function parse(GoogleDom $googleDOM, \DomElement $group, IndexedResultSet $resultSet)
    {
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Flight.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class Flight implements \Serps\SearchEngine\Google\Parser\ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ('flun' == $node->getAttribute('id')) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $googleDOM, \DomElement $group, IndexedResultSet $resultSet)
    {
        $resultSet->addItem(new BaseResult(NaturalResultType::FLIGHTS, []));
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/ImageGroup.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Media\MediaFactory;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\UrlArchive;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class ImageGroup implements \Serps\SearchEngine\Google\Parser\ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($node->hasAttribute('id') && $node->getAttribute('id') == 'imagebox_bigimages') {
            return self::RULE_MATCH_MATCHED;
        } else {
            return self::RULE_MATCH_NOMATCH;
        }
    }
    public function parse(GoogleDom $googleDOM, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = [
            'images' => [],
            'isCarousel' => false,
            'moreUrl' => function () use ($node, $googleDOM) {
                $aTag = $googleDOM->getXpath()->query('descendant::div[@class="_Icb _kk _wI"]/a', $node)->item(0);
                if (!$aTag) {
                    return $googleDOM->getUrl()->resolve('/');
                }
                return $googleDOM->getUrl()->resolveAsString($aTag->getAttribute('href'));
            }
        ];

        // TODO: detect no image (google dom update)
        $imageNodes = $googleDOM->cssQuery('.rg_ul>div._ZGc a', $node);
        foreach ($imageNodes as $imgNode) {
            $item['images'][] = $this->parseItem($googleDOM, $imgNode);
        }
        $resultSet->addItem(new BaseResult(NaturalResultType::IMAGE_GROUP, $item));
    }
    /**
     * @param GoogleDOM $googleDOM
     * @param \DOMElement $imgNode
     * @return array
     */
    private function parseItem(GoogleDom $googleDOM, \DOMElement $imgNode)
    {
        $data =  [
            'sourceUrl' => function () use ($imgNode, $googleDOM) {
                $img = $googleDOM->getXpath()->query('descendant::img', $imgNode)->item(0);
                if (!$img) {
                    return $googleDOM->getUrl()->resolve('/');
                }
                return $googleDOM->getUrl()->resolveAsString($img->getAttribute('title'));
            },
            'targetUrl' => function () use ($imgNode, $googleDOM) {
                return $googleDOM->getUrl()->resolveAsString($imgNode->getAttribute('href'));
            },
            'image' => function () use ($imgNode, $googleDOM) {
                $img = $googleDOM->getXpath()->query('descendant::img', $imgNode)->item(0);
                if (!$img) {
                    return '';
                }
                return MediaFactory::createMediaFromSrc($img->getAttribute('src'));
            },
        ];

        return new BaseResult(NaturalResultType::IMAGE_GROUP_IMAGE, $data);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/ImageGroupCarousel.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Media\MediaFactory;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\UrlArchive;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class ImageGroupCarousel implements \Serps\SearchEngine\Google\Parser\ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('._ekh image-viewer-group g-scrolling-carousel', $node)->length == 1) {
            return self::RULE_MATCH_MATCHED;
        } else {
            return self::RULE_MATCH_NOMATCH;
        }
    }
    public function parse(GoogleDom $googleDOM, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = [
            'images' => function () use ($node, $googleDOM) {
                $items = [];

                $imageNodes = $googleDOM->cssQuery('.rg_ul>._sqh g-inner-card', $node);
                foreach ($imageNodes as $imageNode) {
                    $items[] = $this->parseItem($googleDOM, $imageNode);
                }

                return $items;
            },
            'isCarousel' => true,
            'moreUrl' => function () use ($node, $googleDOM) {
                $a = $googleDOM->cssQuery('g-tray-header ._Nbi a');
                $a = $a->item(0);
                if ($a instanceof \DOMElement) {
                    return $googleDOM->getUrl()->resolveAsString($a->getAttribute('href'));
                }
                return null;
            }
        ];

        $resultSet->addItem(new BaseResult(NaturalResultType::IMAGE_GROUP, $item));
    }
    /**
     * @param GoogleDOM $googleDOM
     * @param \DOMElement $imgNode
     * @return array
     *
     */
    private function parseItem(GoogleDom $googleDOM, \DOMElement $imgNode)
    {
        $data =  [
            'sourceUrl' => function () use ($imgNode, $googleDOM) {
                $node = $googleDOM->cssQuery('.rg_meta', $imgNode)->item(0);
                if (!$node) {
                    return null;
                }
                $url = $googleDOM->getJsonNodeProperty('ru', $node);
                return $url;
            },
            'targetUrl' => function () use ($imgNode, $googleDOM) {
                // not available for mobile results
                return null;
            },
            'image' => function () use ($imgNode, $googleDOM) {
                // TODO: maybe parse from javascript source
                $img = $googleDOM->cssquery('.iuth>img')->item(0);
                if (!$img) {
                    return null;
                }
                return MediaFactory::createMediaFromSrc($img->getattribute('src'));
            },
        ];

        return new BaseResult(NaturalResultType::IMAGE_GROUP_IMAGE, $data);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/InTheNews.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\UrlArchive;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class InTheNews implements \Serps\SearchEngine\Google\Parser\ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        $child = $node->firstChild;
        if (!$child || !($child instanceof \DOMElement)) {
            return self::RULE_MATCH_NOMATCH;
        }
        if ($child->getAttribute('class') == 'mnr-c _yE') {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $googleDOM, \DomElement $group, IndexedResultSet $resultSet)
    {
        $item = [
            'news' => []
        ];
        $xpathCards = "div/div[contains(concat(' ',normalize-space(@class),' '),' card-section ')]";
        $cardNodes = $googleDOM->getXpath()->query($xpathCards, $group);

        foreach ($cardNodes as $cardNode) {
            $item['news'][] = $this->parseItem($googleDOM, $cardNode);
        }

        $resultSet->addItem(new BaseResult(NaturalResultType::IN_THE_NEWS, $item));
    }
    /**
     * @param GoogleDOM $googleDOM
     * @param \DomElement $node
     * @return array
     */
    protected function parseItem(GoogleDom $googleDOM, \DomElement $node)
    {
        $card = [];
        $xpathTitle = "descendant::a[@class = '_Dk']";
        $aTag = $googleDOM->getXpath()->query($xpathTitle, $node)->item(0);
        if ($aTag) {
            $card['title'] = $aTag->nodeValue;
            $card['url'] = $aTag->getAttribute('href');
            $card['description'] = function () use ($googleDOM, $node) {
                $span = $googleDOM->getXpath()->query("descendant::span[@class='_dwd st s std']", $node);
                if ($span && $span->length > 0) {
                    return  $span->item(0)->nodeValue;
                }
                return null;
            };
        }
        return new BaseResult('', $card);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/KnowledgeCard.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class KnowledgeCard implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        if ($node->hasClass('mnr-c') && $node->hasClass('kno-kp')) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $googleDOM, \DomElement $node, IndexedResultSet $resultSet)
    {

        $data = [
            'title' => function () use ($googleDOM, $node) {
                $item = $googleDOM->cssQuery('._OKe ._Q1n ._sdf');

                if (!$item->length) {
                    $item = $googleDOM->cssQuery('.d1rFIf>.kno-ecr-pt>span');
                }

                return $item->getNodeAt(0)->getNodeValue();
            },
            'shortDescription' => function () use ($googleDOM, $node) {
                $item = $googleDOM->cssQuery('._OKe ._Q1n ._gdf', $node); // appears to be outdated

                if (!$item->length) {
                    $item = $googleDOM->cssQuery('.sthby', $node);
                }

                return $item->getNodeAt(0)->getNodeValue();
            }
        ];

        $resultSet->addItem($a = new BaseResult(NaturalResultType::KNOWLEDGE, $data));
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/Map.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class Map implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('.AEprdc.vk_c', $node)->length == 1) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $item = [
            'localPack' => function () use ($node, $dom) {
                $localPackNodes = $dom->cssQuery('.ccBEnf>div', $node);
                $data = [];
                foreach ($localPackNodes as $localPack) {
                    $data[] = new BaseResult(NaturalResultType::MAP_PLACE, $this->parseItem($localPack, $dom));
                }
                return $data;
            },
            'mapUrl'    => function () use ($node, $dom) {
                $mapATag = $dom->cssQuery('#lu_map', $node)->item(0)->parentNode;
                if ($mapATag) {
                    return $dom->getUrl()->resolveAsString($mapATag->getAttribute('href'));
                }
                return null;
            }

        ];

        $resultSet->addItem(new BaseResult(NaturalResultType::MAP, $item));
    }

    private function parseItem($localPack, GoogleDom $dom)
    {

        return [
            'title' => function () use ($localPack, $dom) {
                return $dom->cssQuery('.dbg0pd', $localPack)->getNodeAt(0)->getNodeValue();
            },
            'url' => function () use ($localPack, $dom) {

                // we search for explicit <a> with href to the website.
                // if not found the url is sometimes in a <link> tag
                $nodes = $dom->cssQuery('a.L48Cpd', $localPack);
                if ($nodes->length > 0) {
                    return $nodes->getNodeAt(0)->getAttribute('href');
                } else {
                    return $dom->cssQuery('link[href]', $localPack)
                        ->getNodeAt(0)
                        ->getAttribute('href');
                }
            },
            'street' => function () use ($localPack, $dom) {
                $v = $dom->cssQuery(
                    '.rllt__details>div:nth-child(3)>span',
                    $localPack
                )->getNodeAt(0)->getNodeValue();

                if ($v) {
                    return $v;
                } else {
                    return $dom->cssQuery(
                        '.rllt__details>div:nth-child(1)>span',
                        $localPack
                    )->getNodeAt(0)->getNodeValue();
                }
            },
            'stars' => function () use ($localPack, $dom) {
                $rating = $dom->cssQuery('.BTtC6e', $localPack)->getNodeAt(0)->getNodeValue();

                // transforms "4,4" to 4.4
                return $rating ? (float)str_replace(',', '.', $rating) : null;
            },

            'review' => function () use ($localPack, $dom) {
                $review = $dom->cssQuery(
                    '.BTtC6e',
                    $localPack
                )->getNodeAt(0);

                if ($review instanceof DomElement) {
                    $value = $review->parentNode->getNodeValue();
                } else {
                    return null;
                }

                if ($value && preg_match('/(\([0-9 ,\.]+\))/', $value, $matches)) {
                    // transform '(1 000)' or '(1,000)', etc... to 1000
                    return (int) preg_replace('/[^0-9]/', '', $matches[1]);
                }

                return null;
            },

            'phone' => function () use ($localPack, $dom) {
                $item = $dom->cssQuery(
                    '.rllt__details>div:nth-child(3)',
                    $localPack
                )->item(0);

                if (!$item) {
                    $item = $dom->cssQuery(
                        '.rllt__details>div:nth-child(1)',
                        $localPack
                    )->item(0);
                }

                if ($item) {
                    if ($item->childNodes->length > 1 && $item->childNodes->item(1) instanceof \DOMText) {
                        return trim($item->childNodes->item(1)->nodeValue, ' ·');
                    }
                }
                return null;
            },
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/MapLegacy.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\UrlArchive;
use Serps\SearchEngine\Google\GoogleUrlArchive;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

/**
 * This is kept for BC reasons, to be removed in the future
 * TODO
 * @deprecated
 */
class MapLegacy implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('._RBh', $node)->length  > 1) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $xPath = $dom->getXpath();

        $item = [
            'localPack' => function () use ($xPath, $node, $dom) {
                $localPackNodes = $xPath->query('descendant::div[@class="_gt"]', $node);
                $data = [];
                foreach ($localPackNodes as $localPack) {
                    $data[] = new BaseResult(NaturalResultType::MAP_PLACE, $this->parseItem($localPack, $dom));
                }
                return $data;
            },
            'mapUrl'    => function () use ($xPath, $node, $dom) {
                $mapATag = $dom->cssQuery('#lu_map', $node)->item(0)->parentNode;
                if ($mapATag) {
                    return $dom->getUrl()->resolveAsString($mapATag->getAttribute('href'));
                }
                return null;
            }

        ];

        $resultSet->addItem(new BaseResult(NaturalResultType::MAP, $item));
    }

    private function parseItem($localPack, GoogleDom $dom)
    {

        return [
            'title' => function () use ($localPack, $dom) {
                $item = $dom->cssQuery('._rl', $localPack)->item(0);
                if ($item) {
                    return $item->nodeValue;
                }
                return null;
            },
            'url' => function () use ($localPack, $dom) {
                $item = $dom->getXpath()->query('descendant::a', $localPack)->item(1);
                if ($item) {
                    return $item->getAttribute('href');
                }
                return null;
            },
            'street' => function () use ($localPack, $dom) {
                $item = $dom->cssQuery(
                    '._iPk>span.rllt__details>div:nth-child(3)>span',
                    $localPack
                )->item(0);
                if ($item) {
                    return $item->nodeValue;
                }
                return null;
            },

            'stars' => function () use ($localPack, $dom) {
                $item = $dom->cssQuery('._PXi', $localPack)->item(0);
                if ($item) {
                    return $item->nodeValue;
                }
                return null;
            },

            'review' => function () use ($localPack, $dom) {
                $item = $dom->cssQuery(
                    '._iPk>span.rllt__details>div:nth-child(1)',
                    $localPack
                )->item(0);
                if ($item) {
                    if ($item->childNodes->length > 0 && !($item->childNodes->item(0) instanceof \DOMText)) {
                        return null;
                    } else {
                        return trim(explode('·', $item->nodeValue)[0]);
                    }
                }
                return null;
            },

            'phone' => function () use ($localPack, $dom) {
                $item = $dom->cssQuery(
                    '._iPk>span.rllt__details>div:nth-child(3)',
                    $localPack
                )->item(0);
                if ($item) {
                    if ($item->childNodes->length > 1 && $item->childNodes->item(1) instanceof \DOMText) {
                        return trim($item->childNodes->item(1)->nodeValue, ' ·');
                    }
                }
                return null;
            },
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/MapMobile.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Dom\DomElement;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class MapMobile implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('img.wfAGXd', $node)->length == 1) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $item = [
            'localPack' => function () use ($node, $dom) {
                $localPackNodes = $dom->cssQuery('.PX16ld', $node);
                $data = [];
                foreach ($localPackNodes as $localPack) {
                    $data[] = new BaseResult(NaturalResultType::MAP_PLACE, $this->parseItem($localPack, $dom));
                }
                return $data;
            },
            'mapUrl'    => function () use ($node, $dom) {
                return null;
            }

        ];

        $resultSet->addItem(new BaseResult(NaturalResultType::MAP, $item));
    }

    private function parseItem($localPack, GoogleDom $dom)
    {

        return [
            'title' => function () use ($localPack, $dom) {
                return $dom->cssQuery('.kR1eme', $localPack)->getNodeAt(0)->getNodeValue();
            },
            'url' => function () use ($localPack, $dom) {
                $nodes = $dom->cssQuery('a', $localPack);

                $href = $nodes
                    ->getNodeAt(0)
                    ->getAttribute('href');

                if ($href) {
                    return $dom->getUrl()->resolveAsString($href);
                }
            },
            'street' => function () use ($localPack, $dom) {
                // TODO
                return null;
            },
            'stars' => function () use ($localPack, $dom) {
                $rating = $dom->cssQuery('.BTtC6e', $localPack)->getNodeAt(0)->getNodeValue();
                // transforms "4,4" to 4.4
                return $rating ? (float)str_replace(',', '.', $rating) : null;
            },

            'review' => function () use ($localPack, $dom) {
                $review = $dom->cssQuery(
                    '.BTtC6e',
                    $localPack
                )->getNodeAt(0);

                if ($review instanceof DomElement) {
                    $value = $review->parentNode->getNodeValue();
                } else {
                    return null;
                }

                if ($value && preg_match('/(\([0-9 ,\.]+\))/', $value, $matches)) {
                    // transform '(1 000)' or '(1,000)', etc... to 1000
                    return (int) preg_replace('/[^0-9]/', '', $matches[1]);
                }

                return null;
            },

            'phone' => function () use ($localPack, $dom) {
                return null;
            },
        ];
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/PeopleAlsoAsk.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Dom\DomElement;
use Serps\Core\Dom\DomNodeList;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

/**
 * Class PeopleAlsoAsk
 * @package Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural
 *
 * Note: "people also ask" would results would also match against "Knowledge" parser.
 *       For this reason "paa" parser must be processed before knowledge parser.
 *
 */
class PeopleAlsoAsk implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, DomElement $node)
    {
        if ($node->hasClasses(['kno-kp', 'mnr-c'])) {
            $childNodes = new DomNodeList($node->childNodes, $dom);

            if ($childNodes->hasAnyClass(['cUnQKe', '_thf'])) {  // TODO "_thf" kept for BC, remove in future
                return self::RULE_MATCH_MATCHED;
            }
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $data = [
            'questions' => function () use ($dom, $node) {
                $items = [];
                $nodes = $dom->cssQuery('.related-question-pair', $node);
                foreach ($nodes as $questionNode) {
                    $items[] = new BaseResult(NaturalResultType::PAA_QUESTION, [
                        'question' => function () use ($questionNode, $dom) {
                            return $questionNode->getNodeValue();
                        }
                    ]);
                }
                return $items;
            }
        ];

        $resultSet->addItem($a = new BaseResult(NaturalResultType::PEOPLE_ALSO_ASK, $data));
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/SearchResultGroup.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

/**
 * This is a group of results that need to be sub-parsed
 */
class SearchResultGroup implements ParsingRuleInterface
{
    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($node->hasAnyClass(['srg', '_NId', 'bkWMgd'])) {
            return $node->childNodes;
        } else {
            return self::RULE_MATCH_NOMATCH;
        }
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/TopStoriesCarousel.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\Serp\ResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class TopStoriesCarousel implements ParsingRuleInterface
{
    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('h3._MRj', $node)->length == 1
            && $dom->cssQuery('g-scrolling-carousel._Ncr', $node)->length == 1
            // Dont use _JTg or _bfj class because it's common to all carousel
        ) {
            return self::RULE_MATCH_MATCHED;
        }


        return self::RULE_MATCH_NOMATCH;
    }

    private function parseNode(GoogleDom $dom, $node)
    {
        return [
            'isCarousel' => true,
            'isVertical' => false,
            'news' => function () use ($dom, $node) {

                $news = [];
                $nodes = $dom->cssQuery('._Ocr>._Pcr', $node);

                foreach ($nodes as $newsNode) {
                    $news[] = new BaseResult(NaturalResultType::TOP_STORIES_NEWS_CAROUSEL, [
                        'title' => function () use ($dom, $newsNode) {
                            $el = $dom->cssQuery('._IRj', $newsNode)->item(0);
                            return $el->nodeValue;
                        },
                        'url' => function () use ($dom, $newsNode) {
                            $el = $dom->cssQuery('g-inner-card._KBh>a', $newsNode)->item(0);
                            return $el->getAttribute('href');
                        }
                    ]);
                }

                $resultSet = new ResultSet();
                $resultSet->addItems($news);

                return $resultSet;
            }
        ];
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = new BaseResult(
            [NaturalResultType::TOP_STORIES],
            $this->parseNode($dom, $node)
        );
        $resultSet->addItem($item);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/TopStoriesVertical.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\Core\Serp\ResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

class TopStoriesVertical implements ParsingRuleInterface
{
    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('h3._MRj', $node)->length == 1
            && $dom->cssQuery('g-scrolling-carousel._Ncr', $node)->length == 0
        ) {
            return self::RULE_MATCH_MATCHED;
        }


        return self::RULE_MATCH_NOMATCH;
    }

    private function parseNode(GoogleDom $dom, $node)
    {
        return [
            'isCarousel' => false,
            'isVertical' => true,
            'news' => function () use ($dom, $node) {

                $news = [];
                $nodes = $dom->cssQuery('._KBh', $node);

                foreach ($nodes as $newsNode) {
                    $news[] = new BaseResult(NaturalResultType::TOP_STORIES_NEWS_VERTICAL, [
                        'title' => function () use ($dom, $newsNode) {
                            $el = $dom->cssQuery('a', $newsNode)->item(0);
                            return $el->nodeValue;
                        },
                        'url' => function () use ($dom, $newsNode) {
                            $el = $dom->cssQuery('a', $newsNode)->item(0);
                            return $el->getAttribute('href');
                        }
                    ]);
                }

                $resultSet = new ResultSet();
                $resultSet->addItems($news);

                return $resultSet;
            }
        ];
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $item = new BaseResult(
            [NaturalResultType::TOP_STORIES],
            $this->parseNode($dom, $node)
        );
        $resultSet->addItem($item);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/TweetsCarousel.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

class TweetsCarousel implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {

        if ($dom->cssQuery('.g ._BOf', $node)->length) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {
        $xpath = $dom->getXpath();

        /* @var $aTag \DOMElement */
        $aTag=$xpath
            ->query("descendant::h3[@class='r'][1]//a", $node)
            ->item(0);

        if ($aTag) {
            $title = $aTag->nodeValue;

            preg_match('/@([A-Za-z0-9_]{1,15})/', $title, $match);

            $data = [
                'title'   => $title,
                'url'     => $aTag->getAttribute('href'),
                'user'    => isset($match[0]) ? $match[0] : null
            ];

            $item = new BaseResult(NaturalResultType::TWEETS_CAROUSEL, $data);
            $resultSet->addItem($item);
        }
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/TweetsCarouselZ1m.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Exception;
use Serps\SearchEngine\Google\Exception\InvalidDOMException;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;
use Serps\SearchEngine\Google\NaturalResultType;

/**
 * A slightly different implementation of the twitter carousel that is found on mobile results under the element ._Z1m
 */
class TweetsCarouselZ1m implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {

        if ($node->childNodes->length == 1) {
            $childNode = $node->getChildren()->getNodeAt(0);

            $subChildNode = $childNode->getChildren()->getNodeAt(0);

            if ($childNode->hasClass('_Z1m') && $subChildNode->hasClass('_ujp')) {
                return self::RULE_MATCH_MATCHED;
            }
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $item = new BaseResult(NaturalResultType::TWEETS_CAROUSEL, [

            'url' => function () use ($dom, $node) {
                $res = $dom->cssQuery('._Z1m>._ujp>a', $node);

                if ($res->length == 1) {
                    return $res->item(0)->getAttribute('href');
                } else {
                    throw new InvalidDOMException('Cannot parse url for twitter carousel.');
                }
            },

            'title' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('._ees', $node)
                    ->item(0)
                    ->nodeValue;
            },

            'destination' => function () use ($dom, $node) {
                return $dom
                    ->cssQuery('span._Clt', $node)
                    ->item(0)
                    ->nodeValue;
            },

            'user' => function (BaseResult $result) {
                $url = $result->getDataValue('url');

                $match = preg_match('~twitter.com/([^/?#]+)~', $url, $matches);

                if ($match) {
                    return '@' . $matches[1];
                }

                return null;
            }
        ]);
        $resultSet->addItem($item);
    }
}


================================================
FILE: src/Parser/Evaluated/Rule/Natural/VideoGroup.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser\Evaluated\Rule\Natural;

use Serps\Core\Media\MediaFactory;
use Serps\Core\Serp\BaseResult;
use Serps\Core\Serp\IndexedResultSet;
use Serps\SearchEngine\Google\NaturalResultType;
use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\SearchEngine\Google\Parser\ParsingRuleInterface;

/**
 * This rule extracts video groups as present on mobile results
 */
class VideoGroup implements ParsingRuleInterface
{

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    {
        if ($dom->cssQuery('._Fzo', $node)->length == 1) {
            return self::RULE_MATCH_MATCHED;
        }
        return self::RULE_MATCH_NOMATCH;
    }

    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet)
    {

        $item = [

            'videos'    => function () use ($node, $dom) {
                $items = [];
                $nodes = $dom->cssQuery('._ERj', $node);

                foreach ($nodes as $node) {
                    $items[] = new BaseResult(NaturalResultType::VIDEO_GROUP_VIDEO, [
                        'image' => function () use ($node, $dom) {
                            $data = $dom->cssQuery('g-img img', $node)->getNodeAt(0)->getAttribute('src');
                            return MediaFactory::createMediaFromSrc($data);
                        },
                        'title' => function () use ($node, $dom) {
                            return $dom->cssQuery('._IRj', $node)->getNodeAt(0)->getNodeValue();
                        },
                        'url' => function () use ($node, $dom) {
                            return $dom->cssQuery('g-inner-card a', $node)->getNodeAt(0)->getAttribute('href');
                        }
                    ]);
                }

                return $items;
            }

        ];

        $resultSet->addItem(new BaseResult(NaturalResultType::VIDEO_GROUP, $item));
    }
}


================================================
FILE: src/Parser/ParserInterface.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser;

use Serps\Core\Serp\ResultSetInterface;
use Serps\SearchEngine\Google\Page\GoogleDom;

interface ParserInterface
{

    /**
     * @param GoogleDom $googleDom
     * @return ResultSetInterface
     */
    public function parse(GoogleDom $googleDom);
}


================================================
FILE: src/Parser/ParsingRuleInterface.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\SearchEngine\Google\Parser;

use Serps\SearchEngine\Google\Page\GoogleDom;
use Serps\Core\Serp\IndexedResultSet;

interface ParsingRuleInterface
{
    const RULE_MATCH_MATCHED = 1;
    const RULE_MATCH_NOMATCH = 2;
    const RULE_MATCH_STOP = 3;

    public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node);
    public function parse(GoogleDom $dom, \DomElement $node, IndexedResultSet $resultSet);
}


================================================
FILE: stubs/RelatedSearch.php
================================================
<?php
/**
 * @license see LICENSE
 */

namespace Serps\Stubs;


class RelatedSearch
{

    /**
     * @var string url of the related search
     */
    public $url;

    /**
     * @var string title of the related search
     */
    public $title;
}


================================================
FILE: test/bin/ci.bash
================================================
#!/bin/bash

set -e

SCRIPTFILE=$(readlink -f "$0")
SCRIPTDIR=$(dirname "$SCRIPTFILE")


echo -e "\e[34m"
echo "======================"
echo -e "= \e[1m\e[33mRunning unit tests\e[0m\e[34m ="
echo -e "======================\e[39m"

php $SCRIPTDIR/../../vendor/bin/phpunit -c "$SCRIPTDIR/../../phpunit.dist.xml"


echo -e "\e[34m"
echo "================================="
echo -e "= \e[1m\e[33mChecking code style standards\e[0m\e[34m ="
echo -e "=================================\e[39m"

$SCRIPTDIR/phpcs.bash $1

echo "Code standards: OK"


echo -e "\e[34m"
echo "============================"
echo -e "= \e[1m\e[33mResporting code coverage\e[0m\e[34m ="
echo -e "============================\e[39m"
if [ "$PROCESS_CODECLIMATE" = true ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ]
then
    composer require codeclimate/php-test-reporter:dev-master
    ./vendor/bin/test-reporter
else
    echo "Skip code coverage report..."
fi


================================================
FILE: test/bin/phpcbf.bash
================================================
#!/bin/bash

SCRIPTFILE=$(readlink -f "$0")
SCRIPTDIR=$(dirname "$SCRIPTFILE")

cd $SCRIPTDIR/../.. && $SCRIPTDIR/../../vendor/bin/phpcbf --standard="$SCRIPTDIR/../../phpcs.xml"

exit 0


================================================
FILE: test/bin/phpcs.bash
================================================
#!/bin/bash

SCRIPTFILE=$(readlink -f "$0")
SCRIPTDIR=$(dirname "$SCRIPTFILE")


if [ -n "$1" ]; then report=$1; else report="summary"; fi
cd $SCRIPTDIR/../.. && $SCRIPTDIR/../../vendor/bin/phpcs --standard="$SCRIPTDIR/../../phpcs.xml" --report=$report -s



================================================
FILE: test/bin/test.bash
================================================
#!/bin/bash

SCRIPTFILE=$(readlink -f "$0")
SCRIPTDIR=$(dirname "$SCRIPTFILE")


if [ -n "$1" ]; then filter=$1; else filter="."; fi
cd $SCRIPTDIR/../.. && $SCRIPTDIR/../../vendor/bin/phpunit -c phpunit.dist.xml --filter $filter


================================================
FILE: test/resources/pages-evaluated/2018/03/asian+massage.html
================================================
<!DOCTYPE html>
<?xml encoding="UTF-8"><html itemscope="" itemtype="http://schema.org/SearchResultsPage" lang="en-GB"><head><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><link href="/images/branding/product/ico/googleg_lodp.ico" rel="shortcut icon"><meta content="origin" name="referrer"><title>asian massage - Google Search</title><script nonce="KYI43wH9N6dmn39dV/OrTw==">(function(){window.google={kEI:'mvm4Wo7vNYnmUpTutfgB',kEXPI:'31',authuser:0,kscs:'c9c918f0_mvm4Wo7vNYnmUpTutfgB',u:'c9c918f0',kGL:'GB'};google.kHL='en-GB';})();(function(){google.lc=[];google.li=0;google.getEI=function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||google.kEI};google.getLEI=function(a){for(var b=null;a&&(!a.getAttribute||!(b=a.getAttribute("leid")));)a=a.parentNode;return b};google.https=function(){return"https:"==window.location.protocol};google.ml=function(){return null};google.wl=function(a,b){try{google.ml(Error(a),!1,b)}catch(d){}};google.time=function(){return(new Date).getTime()};google.log=function(a,b,d,c,g){if(a=google.logUrl(a,b,d,c,g)){b=new Image;var e=google.lc,f=google.li;e[f]=b;b.onerror=b.onload=b.onabort=function(){delete e[f]};google.vel&&google.vel.lu&&google.vel.lu(a);b.src=a;google.li=f+1}};google.logUrl=function(a,b,d,c,g){var e="",f=google.ls||"";d||-1!=b.search("&ei=")||(e="&ei="+google.getEI(c),-1==b.search("&lei=")&&(c=google.getLEI(c))&&(e+="&lei="+c));c="";!d&&google.cshid&&-1==b.search("&cshid=")&&(c="&cshid="+google.cshid);a=d||"/"+(g||"gen_204")+"?atyp=i&ct="+a+"&cad="+b+e+f+"&zx="+google.time()+c;/^http:/i.test(a)&&google.https()&&(google.ml(Error("a"),!1,{src:a,glmm:1}),a="");return a};}).call(this);(function(){google.y={};google.x=function(a,b){if(a)var c=a.id;else{do c=Math.random();while(google.y[c])}google.y[c]=[a,b];return!1};google.lm=[];google.plm=function(a){google.lm.push.apply(google.lm,a)};google.lq=[];google.load=function(a,b,c){google.lq.push([[a],b,c])};google.loadAll=function(a,b){google.lq.push([a,b])};}).call(this);google.f={};(function(){google.hs={h:true};})();google.arwt=function(a){a.href=document.getElementById(a.id.substring(1)).href;return!0};(function(){var k=this,l=function(){},t=Date.now||function(){return+new Date};var v={};var w=function(a,d){if(null===d)return!1;if("contains"in a&&1==d.nodeType)return a.contains(d);if("compareDocumentPosition"in a)return a==d||!!(a.compareDocumentPosition(d)&16);for(;d&&a!=d;)d=d.parentNode;return d==a};var B=function(a,d){return function(b){b||(b=window.event);return d.call(a,b)}},C=function(a){a=a.target||a.srcElement;!a.getAttribute&&a.parentNode&&(a=a.parentNode);return a},D="undefined"!=typeof navigator&&/Macintosh/.test(navigator.userAgent),E="undefined"!=typeof navigator&&!/Opera/.test(navigator.userAgent)&&/WebKit/.test(navigator.userAgent),aa={A:1,INPUT:1,TEXTAREA:1,SELECT:1,BUTTON:1},ba=function(){this._mouseEventsPrevented=!0},F={A:13,BUTTON:0,CHECKBOX:32,COMBOBOX:13,GRIDCELL:13,LINK:13,LISTBOX:13,MENU:0,MENUBAR:0,MENUITEM:0,MENUITEMCHECKBOX:0,MENUITEMRADIO:0,OPTION:0,RADIO:32,RADIOGROUP:32,RESET:0,SUBMIT:0,TAB:0,TREE:13,TREEITEM:13},G=function(a){return(a.getAttribute("type")||a.tagName).toUpperCase()in ca},H=function(a){return(a.getAttribute("type")||a.tagName).toUpperCase()in da},ca={CHECKBOX:!0,OPTION:!0,RADIO:!0},da={COLOR:!0,DATE:!0,DATETIME:!0,"DATETIME-LOCAL":!0,EMAIL:!0,MONTH:!0,NUMBER:!0,PASSWORD:!0,RANGE:!0,SEARCH:!0,TEL:!0,TEXT:!0,TEXTAREA:!0,TIME:!0,URL:!0,WEEK:!0},ea={A:!0,AREA:!0,BUTTON:!0,DIALOG:!0,IMG:!0,INPUT:!0,LINK:!0,MENU:!0,OPTGROUP:!0,OPTION:!0,PROGRESS:!0,SELECT:!0,TEXTAREA:!0};var I=function(){this.i=this.g=null},K=function(a,d){var b=J;b.g=a;b.i=d;return b};I.prototype.h=function(){var a=this.g;this.g&&this.g!=this.i?this.g=this.g.__owner||this.g.parentNode:this.g=null;return a};var L=function(){this.l=[];this.g=0;this.i=null;this.o=!1};L.prototype.h=function(){if(this.o)return J.h();if(this.g!=this.l.length){var a=this.l[this.g];this.g++;a!=this.i&&a&&a.__owner&&(this.o=!0,K(a.__owner,this.i));return a}return null};var J=new I,M=new L;var P=function(){this.w=[];this.g=[];this.h=[];this.o={};this.i=null;this.l=[];this.C=l;O(this,"_custom")},fa="undefined"!=typeof navigator&&/iPhone|iPad|iPod/.test(navigator.userAgent),Q=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^\s+/,"").replace(/\s+$/,"")},ha=/\s*;\s*/,la=function(a,d){return function(b){var c=d;if("_custom"==c){c=b.detail;if(!c||!c._type)return;c=c._type}if("click"==c&&(D&&b.metaKey||!D&&b.ctrlKey||2==b.which||null==b.which&&4==b.button||
        "auxclick"==b.type||b.shiftKey))c="clickmod";else{var e=b.which||b.keyCode||b.key;E&&3==e&&(e=13);if(13!=e&&32!=e)e=!1;else{var f=C(b),q=(f.getAttribute("role")||f.type||f.tagName).toUpperCase(),h;(h="keydown"!=b.type)||("getAttribute"in f?(h=(f.getAttribute("role")||f.tagName).toUpperCase(),h=!H(f)&&("COMBOBOX"!=h||"INPUT"!=h)&&!f.isContentEditable):h=!1,h=!h);(h=h||b.ctrlKey||b.shiftKey||b.altKey||b.metaKey||G(f)&&32==e)||((h=f.tagName in aa)||(h=f.getAttributeNode("tabindex"),h=null!=h&&h.specified),h=!(h&&!f.disabled));h?e=!1:(f="INPUT"!=f.tagName.toUpperCase()||f.type,h=!(q in F)&&13==e,e=(0==F[q]%e||h)&&!!f)}e&&(c="clickkey")}q=b.srcElement||b.target;e=R(c,b,q,"",null);b.path?(M.l=b.path,M.g=0,M.i=this,M.o=!1,f=M):f=K(q,this);for(;h=f.h();){var m=h;var g=m;h=c;var p=g.__jsaction;if(!p){var u=null;"getAttribute"in g&&(u=g.getAttribute("jsaction"));if(u){p=v[u];if(!p){p={};for(var x=u.split(ha),y=0,ia=x?x.length:0;y<ia;y++){var r=x[y];if(r){var z=r.indexOf(":"),N=-1!=z,ja=N?Q(r.substr(0,z)):"click";r=N?Q(r.substr(z+1)):r;p[ja]=r}}v[u]=p}g.__jsaction=p}else p=ka,g.__jsaction=p}"clickkey"==h?h="click":"click"!=h||p.click||(h="clickonly");g={v:h,action:p[h]||"",event:null,B:!1};e=R(g.v,g.event||b,q,g.action||"",m,e.timeStamp);if(g.B||g.action)break}e&&"touchend"==e.eventType&&(e.event._preventMouseEvents=ba);if(g&&g.action){if(g="clickkey"==c)g=C(b),g=(g.type||g.tagName).toUpperCase(),(g=32==(b.which||b.keyCode||b.key)&&"CHECKBOX"!=g)||(g=C(b),q=(g.getAttribute("role")||g.tagName).toUpperCase(),g=g.tagName.toUpperCase()in ea&&"A"!=q&&!G(g)&&!H(g)||"BUTTON"==q);g&&(b.preventDefault?b.preventDefault():b.returnValue=!1);if("mouseenter"==c||"mouseleave"==c)if(g=b.relatedTarget,!("mouseover"==b.type&&"mouseenter"==c||"mouseout"==b.type&&"mouseleave"==c)||g&&(g===m||w(m,g)))e.action="",e.actionElement=null;else{c={};for(var n in b)"function"!==typeof b[n]&&"srcElement"!==n&&"target"!==n&&(c[n]=b[n]);c.type="mouseover"==b.type?"mouseenter":"mouseleave";c.target=c.srcElement=m;c.bubbles=!1;e.event=
    c;e.targetElement=m}}else e.action="",e.actionElement=null;m=e;a.i&&(n=R(m.eventType,m.event,m.targetElement,m.action,m.actionElement,m.timeStamp),"clickonly"==n.eventType&&(n.eventType="click"),a.i(n,!0));if(m.actionElement){"A"!=m.actionElement.tagName||"click"!=m.eventType&&"clickmod"!=m.eventType||m.actionElement.hasAttribute("data-unjs")&&null==a.i||(b.preventDefault?b.preventDefault():b.returnValue=!1);if(a.i)a.i(m);else{a.C(m);if((n=k.document)&&!n.createEvent&&n.createEventObject)try{var A=
    n.createEventObject(b)}catch(oa){A=b}else A=b;m.event=A;a.l.push(m)}if("touchend"==m.event.type&&m.event._mouseEventsPrevented){b=m.event;for(var pa in b);t()}}}},R=function(a,d,b,c,e,f){return{eventType:a,event:d,targetElement:b,action:c,actionElement:e,timeStamp:f||t()}},ka={},ma=function(a,d){return function(b){var c=a,e=d,f=!1;"mouseenter"==c?c="mouseover":"mouseleave"==c&&(c="mouseout");if(b.addEventListener){if("focus"==c||"blur"==c||"error"==c||"load"==c)f=!0;b.addEventListener(c,e,f)}else b.attachEvent&&
("focus"==c?c="focusin":"blur"==c&&(c="focusout"),e=B(b,e),b.attachEvent("on"+c,e));return{v:c,s:e,capture:f}}},O=function(a,d){if(!a.o.hasOwnProperty(d)){var b=la(a,d),c=ma(d,b);a.o[d]=b;a.w.push(c);for(b=0;b<a.g.length;++b){var e=a.g[b];e.h.push(c.call(null,e.g))}"click"==d&&O(a,"keydown")}};P.prototype.s=function(a){return this.o[a]};var V=function(a,d){var b=new na(d),c;a:{for(c=0;c<a.g.length;c++)if(S(a.g[c],d)){c=!0;break a}c=!1}if(c)return a.h.push(b),b;T(a,b);a.g.push(b);U(a);return b},U=function(a){for(var d=a.h.concat(a.g),b=[],c=[],e=0;e<a.g.length;++e){var f=a.g[e];W(f,d)?(b.push(f),X(f)):c.push(f)}for(e=0;e<a.h.length;++e)f=a.h[e],W(f,d)?b.push(f):(c.push(f),T(a,f));a.g=c;a.h=b},T=function(a,d){var b=d.g;fa&&(b.style.cursor="pointer");for(b=0;b<a.w.length;++b)d.h.push(a.w[b].call(null,d.g))},Y=function(a,d){a.i=d;a.l&&
(0<a.l.length&&d(a.l),a.l=null)},na=function(a){this.g=a;this.h=[]},S=function(a,d){for(var b=a.g,c=d;b!=c&&c.parentNode;)c=c.parentNode;return b==c},W=function(a,d){for(var b=0;b<d.length;++b)if(d[b].g!=a.g&&S(d[b],a.g))return!0;return!1},X=function(a){for(var d=0;d<a.h.length;++d){var b=a.g,c=a.h[d];b.removeEventListener?b.removeEventListener(c.v,c.s,c.capture):b.detachEvent&&b.detachEvent("on"+c.v,c.s)}a.h=[]};var Z=new P;V(Z,window.document.documentElement);O(Z,"click");O(Z,"focus");O(Z,"focusin");O(Z,"blur");O(Z,"focusout");O(Z,"error");O(Z,"load");O(Z,"change");O(Z,"dblclick");O(Z,"input");O(Z,"keyup");O(Z,"keydown");O(Z,"keypress");O(Z,"mousedown");O(Z,"mouseenter");O(Z,"mouseleave");O(Z,"mouseout");O(Z,"mouseover");O(Z,"mouseup");O(Z,"paste");O(Z,"touchstart");O(Z,"touchend");O(Z,"touchcancel");O(Z,"speech");(function(a){google.jsad=function(d){Y(a,d)};google.jsaac=function(d){return V(a,d)};google.jsarc=function(d){X(d);for(var b=!1,c=0;c<a.g.length;++c)if(a.g[c]===d){a.g.splice(c,1);b=!0;break}if(!b)for(c=0;c<a.h.length;++c)if(a.h[c]===d){a.h.splice(c,1);break}U(a)}})(Z);window.gws_wizbind=function(a){return{trigger:function(d){var b=a.s(d.type);b||(O(a,d.type),b=a.s(d.type));var c=d.target||d.srcElement;b&&b.call(c.ownerDocument.documentElement,d)},bind:function(d){Y(a,d)}}}(Z);}).call(this);(function(){var a=[];google.jsc={xx:a,x:function(b){a.push(b)}};}).call(this);(function(){google.c={c:{a:true,d:true,m:true,n:false}};google.sn='web';(function(){var e=function(a,b,c){a.addEventListener?a.removeEventListener(b,c,!1):a.attachEvent&&a.detachEvent("on"+b,c)},g=function(a,b,c){f.push({g:a,h:b,i:c});a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},f=[];google.timers={};google.startTick=function(a,b){var c=b&&google.timers[b].t?google.timers[b].t.start:google.time();google.timers[a]={t:{start:c},e:{},m:{}};(c=window.performance)&&c.now&&(google.timers[a].wsrt=Math.floor(c.now()))};google.tick=function(a,b,c){google.timers[a]||google.startTick(a);c=void 0!==c?c:google.time();b instanceof Array||(b=[b]);for(var d=0;d<b.length;++d)google.timers[a].t[b[d].clearcut]={key:b[d],ts:c}};google.c.e=function(a,b,c){google.timers[a].e[b]=c};google.c.b=function(a){var b=google.timers.load.m;b[a]&&google.wl("ch_mab",{m:a});b[a]=!0};google.c.u=function(a){var b=google.timers.load.m;if(b[a]){b[a]=!1;for(a in b)if(b[a])return;google.csiReport()}else google.wl("ch_mnb",{m:a})};google.rll=function(a,b,c){var d=function(b){c(b);e(a,"load",d);e(a,"error",d)};g(a,"load",d);b&&g(a,"error",d)};google.ull=function(){for(var a;a=f.shift();)e(a.g,a.h,a.i)};google.afte=!0;google.aft=function(a){google.c.c.a&&google.afte&&(google.timers.aft||google.startTick("aft"),google.timers.aft.t[a.id||a.src||a.name]=google.time())};google.startTick("webaft");google.startTick("load");google.c.b("pr");google.c.b("xe");}).call(this);})();var a=window.location,b=a.href.indexOf("#");if(0<=b){var c=a.href.substring(b+1);/(^|&)q=/.test(c)&&-1==c.indexOf("#")&&a.replace("/search?"+c.replace(/(^|&)fp=[^&]*/g,"")+"&cad=h")};</script><style>[dir='ltr'],[dir='rtl']{unicode-bidi:isolate;unicode-bidi:isolate}bdo[dir='ltr'],bdo[dir='rtl']{unicode-bidi:bidi-override;unicode-bidi:isolate-override;unicode-bidi:isolate-override}#logo{display:block;overflow:hidden;position:relative}#logo img{border:0;}#logo span{background:url(/images/nav_logo242.png) no-repeat;cursor:pointer;overflow:hidden}#logocont{z-index:1;padding-left:13px;padding-right:10px;margin-top:-2px;padding-top:7px}#logocont.ddl{padding-top:3px}.big #logocont{padding-left:13px;padding-right:12px}.sbibod{background-color:#fff;height:44px;vertical-align:top;border-radius:2px;box-shadow:0 2px 2px 0 rgba(0,0,0,0.16),0 0 0 1px rgba(0,0,0,0.08);transition:box-shadow 200ms cubic-bezier(0.4, 0.0, 0.2, 1);}.lst{border:0;margin-top:5px;margin-bottom:0}.lst:focus{outline:none}#lst-ib{color:#000}.gsfi,.lst{font:16px arial,sans-serif;line-height:34px;height:34px !important;}.lst-c{overflow:hidden}#gs_st0{line-height:44px;padding:0 8px;margin-top:-1px;position:static}.srp #gs_st0{padding:0 2px 0 8px}.gsfs{font:16px arial,sans-serif}.lsb{background:transparent;border:0;font-size:0;height:30px;outline:0;text-align:left;width:100%}.sbico{display:inline-block;height:24px;width:24px;cursor:pointer;vertical-align:middle;color:#4285f4}.sbico-c{background:transparent;border:0;float:right;height:44px;line-height:44px;margin-top:-1px;outline:0;padding-right:16px;position:relative;top:-1px}.hp .sbico-c{display:none}#sblsbb{text-align:center;border-bottom-left-radius:0;border-top-left-radius:0;height:44px;margin:0;padding:0;}#sbds{border:0;margin-left:0}.hp .nojsb,.srp .jsb{display:none}.kpbb,.kprb,.kpgb,.kpgrb{border-radius:2px;border-radius:2px;color:#fff}.kpbb:hover,.kprb:hover,.kpgb:hover,.kpgrb:hover{box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);color:#fff}.kpbb:active,.kprb:active,.kpgb:active,.kpgrb:active{box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);box-shadow:inset 0 1px 2px rgba(0,0,0,0.3)}.kpbb{background-color:#4d90fe;background-image:linear-gradient(top,#4d90fe,#4787ed);background-image:linear-gradient(top,#4d90fe,#4787ed);border:1px solid #3079ed}.kpbb:hover{background-color:#357ae8;background-image:linear-gradient(top,#4d90fe,#357ae8);background-image:linear-gradient(top,#4d90fe,#357ae8);border:1px solid #2f5bb7}a.kpbb:link,a.kpbb:visited{color:#fff}.kprb{background-color:#dd4b39;background-image:linear-gradient(top,#dd4b39,#d14836);background-image:linear-gradient(top,#dd4b39,#d14836);border:1px solid #dd4b39}.kprb:hover{background-color:#c53727;background-image:linear-gradient(top,#dd4b39,#c53727);background-image:linear-gradient(top,#dd4b39,#c53727);border:1px solid #b0281a;border-bottom-color:#af301f}.kprb:active{background-color:#b0281a;background-image:linear-gradient(top,#dd4b39,#b0281a);background-image:linear-gradient(top,#dd4b39,#b0281a)}.kpgb{background-color:#3d9400;background-image:linear-gradient(top,#3d9400,#398a00);background-image:linear-gradient(top,#3d9400,#398a00);border:1px solid #29691d}.kpgb:hover{background-color:#368200;background-image:linear-gradient(top,#3d9400,#368200);background-image:linear-gradient(top,#3d9400,#368200);border:1px solid #2d6200}.kpgrb{background-color:#f5f5f5;background-image:linear-gradient(top,#f5f5f5,#f1f1f1);background-image:linear-gradient(top,#f5f5f5,#f1f1f1);border:1px solid #dcdcdc;color:#555}.kpgrb:hover{background-color:#f8f8f8;background-image:linear-gradient(top,#f8f8f8,#f1f1f1);background-image:linear-gradient(top,#f8f8f8,#f1f1f1);border:1px solid #dcdcdc;color:#333}a.kpgrb:link,a.kpgrb:visited{color:#555}#sfopt{display:inline-block;float:right;line-height:normal}.lsd{font-size:11px;position:absolute;top:3px;left:16px}.tsf{background:none}.tsf-p{position:relative;}.logocont{left:0;position:absolute;}.sfibbbc{padding-bottom:2px;padding-top:3px;margin-top:-3px;width:632px}.sbtc{position:relative}.sbibtd{line-height:0;overflow:visible;white-space:nowrap}.sbibps{padding:0 9px 0 16px;padding-top:0 !important;width:586px}.sfopt{height:28px;position:relative}#sform{height:65px}.hp .sfsbc{display:none}#searchform{width:100%}.hp #searchform{position:absolute;top:311px}.srp #searchform{position:absolute;top:20px}#sfdiv{max-width:484px}.hp .big #sfdiv{max-width:568px;}.srp #sfdiv{max-width:none;overflow:hidden}.srp #tsf{position:relative;}.sfsbc{display:inline-block;float:right;margin-right:1px;vertical-align:top;width:42px;margin-right:0}.sfbg{background:#fafafa;height:69px;left:0;position:absolute;width:100%}.sfbgg{background-color:#fafafa;height:65px}#pocs{background:#fff1a8;color:#000;font-size:10pt;margin:0;padding:5px 7px}#pocs.sft{background:transparent;color:#777}#pocs a{color:#11c}#pocs.sft a{color:#36c}#pocs>div{margin:0;padding:0}#cnt{padding-top:20px;}#subform_ctrl{min-height:0px}</style><style id="ostyle">@-moz-keyframes gb__a{0%{opacity:0}50%{opacity:1}}@keyframes gb__a{0%{opacity:0}50%{opacity:1}}.gb_bb{display:none!important}.gb_cb{visibility:hidden}.gb_da .gb_b{background-position:-132px -38px;opacity:.55}.gb_ea .gb_da .gb_b{background-position:-132px -38px}.gb_X .gb_da .gb_b{background-position:-463px -35px;opacity:1}.gb_fa.gb_ga{min-height:196px;overflow-y:auto;width:320px}.gb_ha{-moz-transition:height .2s ease-in-out;transition:height .2s ease-in-out}.gb_ia{background:#fff;margin:0;min-height:100px;padding:28px;padding-right:27px;text-align:left;white-space:normal;width:265px}.gb_ja{background:#f5f5f5;cursor:pointer;height:40px;overflow:hidden}.gb_ka{position:relative}.gb_ja{display:block;line-height:40px;text-align:center;width:320px}.gb_ka{display:block;line-height:40px;text-align:center}.gb_ka.gb_la{line-height:0}.gb_ja,.gb_ja:visited,.gb_ja:active,.gb_ka,.gb_ka:visited{color:rgba(0,0,0,0.87);text-decoration:none}.gb_ka:active{color:rgba(0,0,0,0.87)}#gb a.gb_ja,#gb a.gb_ja:visited,#gb a.gb_ja:active,#gb a.gb_ka,#gb a.gb_ka:visited{color:rgba(0,0,0,0.87);text-decoration:none}#gb a.gb_ka:active{color:rgba(0,0,0,0.87)}.gb_ka,.gb_ia{display:none}.gb_ba,.gb_ba+.gb_ka,.gb_ma .gb_ka,.gb_ma .gb_ia{display:block}.gb_ka:hover,.gb_ka:active,#gb a.gb_ka:hover,#gb a.gb_ka:active{text-decoration:underline}.gb_ka{border-bottom:1px solid #ebebeb;left:28px;width:264px}.gb_ma .gb_ja{display:none}.gb_ka:last-child{border-bottom-width:0}.gb_na .gb_O{display:initial}.gb_na.gb_oa{height:100px;text-align:center}.gb_na.gb_oa img{padding:34px 0;height:32px;width:32px}.gb_na .gb_2{background-image:url('//ssl.gstatic.com/gb/images/p1_c82e4f78.png');background-size:64px 2341px;background-position:0 -828px}.gb_na .gb_2+img{border:0;margin:8px;height:48px;width:48px}.gb_na div.gb_pa{background:#ffa;-moz-border-radius:5px;border-radius:5px;padding:5px;text-align:center}.gb_na.gb_qa,.gb_na.gb_ra{padding-bottom:0}.gb_na.gb_sa,.gb_na.gb_ra{padding-top:0}.gb_na.gb_ra a,.gb_na.gb_sa a{top:0}.gb_ta .gb_ja{margin-top:0;position:static}.gb_ua{display:inline-block}.gb_va{margin:-12px 28px 28px;position:relative;width:264px;-moz-border-radius:2px;border-radius:2px;-moz-box-shadow:0 1px 2px rgba(0,0,0,0.1),0 0 1px rgba(0,0,0,0.1);box-shadow:0 1px 2px rgba(0,0,0,0.1),0 0 1px rgba(0,0,0,0.1)}.gb_4{background-image:url('//ssl.gstatic.com/gb/images/p1_c82e4f78.png');background-size:64px 2341px;display:inline-block;margin:8px;vertical-align:middle;height:64px;width:64px}.gb_wa{color:#262626;display:inline-block;font:13px/18px Arial,sans-serif;margin-right:80px;padding:10px 10px 10px 0;vertical-align:middle;white-space:normal}.gb_xa{font:16px/24px Arial,sans-serif}.gb_ya,#gb#gb .gb_ya{color:#427fed;text-decoration:none}.gb_ya:hover,#gb#gb .gb_ya:hover{text-decoration:underline}.gb_za .gb_ia{position:relative}.gb_za .gb_O{position:absolute;top:28px;left:28px}.gb_ja.gb_Aa{display:none;height:0}.gb_db{background-size:32px 32px;-moz-border-radius:50%;border-radius:50%;display:block;margin:-1px;overflow:hidden;position:relative;height:32px;width:32px;z-index:0}@media (min-resolution:1.25dppx),(-o-min-device-pixel-ratio:5/4),(-webkit-min-device-pixel-ratio:1.25),(min-device-pixel-ratio:1.25){.gb_db::before{display:inline-block;-moz-transform:scale(.5);transform:scale(.5);-moz-transform-origin:left 0;transform-origin:left 0}.gb_Eb::before{display:inline-block;-moz-transform:scale(.5);transform:scale(.5);-moz-transform-origin:left 0;transform-origin:left 0}}.gb_db:hover,.gb_db:focus{-moz-box-shadow:0 1px 0 rgba(0,0,0,.15);box-shadow:0 1px 0 rgba(0,0,0,.15)}.gb_db:active{-moz-box-shadow:inset 0 2px 0 rgba(0,0,0,.15);box-shadow:inset 0 2px 0 rgba(0,0,0,.15)}.gb_db:active::after{background:rgba(0,0,0,.1);-moz-border-radius:50%;border-radius:50%;content:'';display:block;height:100%}.gb_eb{cursor:pointer;line-height:30px;min-width:30px;opacity:.75;overflow:hidden;vertical-align:middle;text-overflow:ellipsis}.gb_b.gb_eb{width:auto}.gb_eb:hover,.gb_eb:focus{opacity:.85}.gb_fb .gb_eb,.gb_fb .gb_gb{line-height:26px}#gb#gb.gb_fb a.gb_eb,.gb_fb .gb_gb{font-size:11px;height:auto}.gb_hb{border-top:4px solid #000;border-left:4px dashed transparent;border-right:4px dashed transparent;display:inline-block;margin-left:6px;opacity:.75;vertical-align:middle}.gb_ib:hover .gb_hb{opacity:.85}.gb_X .gb_eb,.gb_X .gb_hb{opacity:1}#gb#gb.gb_X.gb_X a.gb_eb,#gb#gb .gb_X.gb_X a.gb_eb{color:#fff}.gb_X.gb_X .gb_hb{border-top-color:#fff;opacity:1}.gb_ea .gb_db:hover,.gb_X .gb_db:hover,.gb_ea .gb_db:focus,.gb_X .gb_db:focus{-moz-box-shadow:0 1px 0 rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.2);box-shadow:0 1px 0 rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.2)}.gb_jb .gb_kb,.gb_lb .gb_kb{position:absolute;right:1px}.gb_kb.gb_R,.gb_mb.gb_R,.gb_ib.gb_R{flex:0 1 auto;flex:0 1 main-size}.gb_nb.gb_W .gb_eb{width:30px!important}.gb_ob.gb_cb{display:none}@-moz-keyframes progressmove{0%{margin-left:-100%}to{margin-left:100%}}@keyframes progressmove{0%{margin-left:-100%}to{margin-left:100%}}.gb_pb.gb_bb{display:none}.gb_pb{background-color:#ccc;height:3px;overflow:hidden}.gb_qb{background-color:#f4b400;height:100%;width:50%;-moz-animation:progressmove 1.5s linear 0s infinite;animation:progressmove 1.5s linear 0s infinite}.gb_sb{height:40px;position:absolute;right:-5px;top:-5px;width:40px}.gb_tb .gb_sb,.gb_ub .gb_sb{right:0;top:0}.gb_eb~.gb_vb,.gb_eb~.gb_wb{left:auto;right:6.5px}.gb_xb{outline:none;transform:translateZ(0)}.gb_xb.gb_Za{width:320px}.gb_yb,#gb a.gb_yb.gb_yb,.gb_zb a,#gb .gb_zb.gb_zb a{color:#36c;text-decoration:none}.gb_yb:active,#gb a.gb_yb:active,.gb_yb:hover,#gb a.gb_yb:hover,.gb_zb a:active,#gb .gb_zb a:active,.gb_zb a:hover,#gb .gb_zb a:hover{text-decoration:underline}.gb_Ab{margin:20px;white-space:nowrap}.gb_Bb,.gb_Cb{display:inline-block;vertical-align:top}.gb_xb.gb_Za .gb_Cb{max-width:164px}.gb_Bb{margin-right:20px;position:relative}.gb_Db{-moz-border-radius:50%;border-radius:50%;overflow:hidden}.gb_Eb{background-size:96px 96px;border:none;vertical-align:top;height:96px;width:96px}.gb_ob{background:rgba(78,144,254,.7);bottom:0;color:#fff;font-size:9px;font-weight:bold;left:0;line-height:9px;position:absolute;padding:7px 0;text-align:center;width:96px}.gb_Db .gb_ob{background:rgba(0,0,0,.54)}.gb_Fb{font-weight:bold;margin:-4px 0 1px 0;text-overflow:ellipsis;overflow:hidden}.gb_Ib{color:#666;text-overflow:ellipsis;overflow:hidden}.gb_zb{color:#ccc;margin:6px 0}.gb_xb.gb_Za .gb_zb a{display:block;line-height:24px;margin:0}.gb_xb.gb_Za .gb_zb a:first-child:last-child{line-height:normal}.gb_xb:not(.gb_Za) .gb_zb a{margin:0 10px}.gb_xb:not(.gb_Za) .gb_zb a:first-child{margin-left:0}.gb_xb:not(.gb_Za) .gb_zb a:last-child{margin-right:0}.gb_Cb .gb_Jb{background:#4d90fe;border-color:#3079ed;font-weight:bold;margin:10px 0 0 0;color:#fff}#gb .gb_Cb a.gb_Jb.gb_Jb{color:#fff}.gb_Cb .gb_Jb:hover{background:#357ae8;border-color:#2f5bb7}.gb_Kb.gb_oa{border-top:none}.gb_Kb{background:#f5f5f5;border-top:1px solid #ccc;border-color:rgba(0,0,0,.2);padding:10px 0;width:100%;display:table}.gb_Kb .gb_Jb{margin:0 20px;white-space:nowrap}.gb_Kb>div{display:table-cell;text-align:right}.gb_Kb>div:first-child{text-align:left}.gb_Kb .gb_Lb{display:block;text-align:center}.gb_Mb .gb_vb{border-bottom-color:#fef9db}.gb_Nb{background:#fef9db;font-size:11px;padding:10px 20px;white-space:normal}.gb_Nb b,.gb_yb{white-space:nowrap}.gb_Qb{background:#f5f5f5;border-top:1px solid #ccc;border-top-color:rgba(0,0,0,.2);max-height:230px;overflow:auto}.gb_Qb.gb_Za{max-height:170px}.gb_Qb.gb_Za.gb_Rb{max-height:124px}.gb_Sb{border-top:1px solid #ccc;border-top-color:rgba(0,0,0,.2);display:block;padding:10px 20px;position:relative;white-space:nowrap}.gb_Tb .gb_Sb:focus .gb_Ub{outline:1px dotted #fff}.gb_Sb:hover{background:#eee}.gb_Sb[selected="true"]{overflow:hidden}.gb_Sb[selected="true"]>.gb_Vb{background-color:rgba(117,117,117,.9)}.gb_Sb[selected="true"]>.gb_Wb{display:block;position:absolute;z-index:2}.gb_Wb::-moz-focus-inner{border:0}.gb_Wb{background-color:transparent;border:none;color:#fff;display:none;font-family:Roboto,Arial,sans-serif;font-weight:400;font-size:14px;height:36px;min-width:86px;text-align:center;top:16px;width:auto}.gb_Sb[selected="true"]>.gb_Wb:focus{background-color:rgba(0,0,0,.24);-moz-border-radius:2px;border-radius:2px;outline:0}.gb_Sb[selected="true"]>.gb_Wb:hover,.gb_Sb[selected="true"]>.gb_Wb:focus:hover{background-color:#565656;-moz-border-radius:2px;border-radius:2px}.gb_Sb[selected="true"]>.gb_Wb:active{-moz-border-radius:2px;border-radius:2px;background-color:#212121}.gb_Xb{left:0;margin-left:5%}.gb_Zb{margin-right:5%;right:0}.gb_Sb:first-child,.gb_0b:first-child+.gb_Sb{border-top:0}.gb_0b{display:none}.gb_1b{cursor:default}.gb_1b:hover{background:transparent}.gb_2b{border:none;vertical-align:top;height:48px;width:48px}.gb_Ub{display:inline-block;margin:6px 0 0 10px}.gb_xb.gb_Za .gb_Ub{max-width:222px}.gb_1b .gb_2b,.gb_1b .gb_Ub{opacity:.4}.gb_3b{color:#000;text-overflow:ellipsis;overflow:hidden}.gb_1b .gb_3b{color:#666}.gb_4b{color:#666;text-overflow:ellipsis;overflow:hidden}.gb_5b{color:#666;font-style:italic}.gb_Vb{background-color:transparent;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:1}.gb_Wb:hover{background-color:rgba(100,100,100,0.4)}.gb_6b{background:#f5f5f5;border-top:1px solid #ccc;border-top-color:rgba(0,0,0,.2);display:block;padding:10px 20px}.gb_7b{background-position:-244px 0;display:inline-block;margin:1px 0;vertical-align:middle;height:25px;width:25px}.gb_N .gb_7b::before{left:-244px;top:0}.gb_8b{color:#427fed;display:inline-block;padding:0 25px 0 10px;vertical-align:middle;white-space:normal}.gb_6b:hover .gb_8b{text-decoration:underline}.gb_Kb .gb_Jb:hover{-moz-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-color:#c6c6c6;color:#222;background-color:#fff;background-image:-moz-linear-gradient(top,#fff,#f8f8f8);background-image:-moz-linear-gradient(top,#fff,#f8f8f8);background-image:linear-gradient(top,#fff,#f8f8f8);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff',EndColorStr='#f8f8f8')}.gb_9c{display:inline-block;padding:0 0 0 15px;vertical-align:middle}.gb_9c:first-child,#gbsfw:first-child+.gb_9c{padding-left:0}.gb_Qc{position:relative}.gb_b{display:inline-block;outline:none;vertical-align:middle;-moz-border-radius:2px;border-radius:2px;-moz-box-sizing:border-box;box-sizing:border-box;height:30px;width:30px;color:#000;cursor:pointer;text-decoration:none}#gb#gb a.gb_b{color:#000;cursor:pointer;text-decoration:none}.gb_vb{border-color:transparent;border-bottom-color:#fff;border-style:dashed dashed solid;border-width:0 8.5px 8.5px;display:none;position:absolute;left:6.5px;top:37px;z-index:1;height:0;width:0;-moz-animation:gb__a .2s;animation:gb__a .2s}.gb_wb{border-color:transparent;border-style:dashed dashed solid;border-width:0 8.5px 8.5px;display:none;position:absolute;left:6.5px;z-index:1;height:0;width:0;-moz-animation:gb__a .2s;animation:gb__a .2s;border-bottom-color:#ccc;border-bottom-color:rgba(0,0,0,.2);top:36px}x:-o-prefocus,div.gb_wb{border-bottom-color:#ccc}.gb_fa{background:#fff;border:1px solid #ccc;border-color:rgba(0,0,0,.2);color:#000;-moz-box-shadow:0 2px 10px rgba(0,0,0,.2);box-shadow:0 2px 10px rgba(0,0,0,.2);display:none;outline:none;overflow:hidden;position:absolute;right:0;top:44px;-moz-animation:gb__a .2s;animation:gb__a .2s;-moz-border-radius:2px;border-radius:2px;-moz-user-select:text}.gb_9c.gb_g .gb_vb,.gb_9c.gb_g .gb_wb,.gb_9c.gb_g .gb_fa,.gb_g.gb_fa{display:block}.gb_9c.gb_g.gb_Bf .gb_vb,.gb_9c.gb_g.gb_Bf .gb_wb{display:none}.gb_Cf{position:absolute;right:0;top:44px;z-index:-1}.gb_fb .gb_vb,.gb_fb .gb_wb,.gb_fb .gb_fa{margin-top:-10px}#gbsfw{min-width:400px;overflow:visible}.gb_9b,#gbsfw.gb_g{display:block;outline:none}#gbsfw.gb_pa iframe{display:none}.gb_ac{padding:118px 0;text-align:center}.gb_bc{background:no-repeat center 0;color:#aaa;font-size:13px;line-height:20px;padding-top:76px;background-image:url('//ssl.gstatic.com/gb/images/a/f5cdd88b65.png')}.gb_bc a{color:#4285f4;text-decoration:none}@-moz-keyframes gb__nb{0%{-moz-transform:scale(0,0);transform:scale(0,0)}20%{-moz-transform:scale(1.4,1.4);transform:scale(1.4,1.4)}50%{-moz-transform:scale(.8,.8);transform:scale(.8,.8)}85%{-moz-transform:scale(1.1,1.1);transform:scale(1.1,1.1)}to{-moz-transform:scale(1.0,1.0);transform:scale(1.0,1.0)}}@keyframes gb__nb{0%{-moz-transform:scale(0,0);transform:scale(0,0)}20%{-moz-transform:scale(1.4,1.4);transform:scale(1.4,1.4)}50%{-moz-transform:scale(.8,.8);transform:scale(.8,.8)}85%{-moz-transform:scale(1.1,1.1);transform:scale(1.1,1.1)}to{-moz-transform:scale(1.0,1.0);transform:scale(1.0,1.0)}}.gb_Hc{background-position:-314px -38px;opacity:.55;height:100%;width:100%}.gb_b:hover .gb_Hc,.gb_b:focus .gb_Hc{opacity:.85}.gb_Ic .gb_Hc{background-position:-463px 0}.gb_Jc{background-color:#cb4437;-moz-border-radius:8px;border-radius:8px;font:bold 11px Arial;color:#fff;line-height:16px;min-width:14px;padding:0 1px;position:absolute;right:0;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,0.1);top:0;visibility:hidden;z-index:990}.gb_Kc .gb_Jc,.gb_Kc .gb_Lc,.gb_Kc .gb_Lc.gb_Mc{visibility:visible}.gb_Lc{padding:0 2px;visibility:hidden}.gb_Nc:not(.gb_Oc) .gb_wb,.gb_Nc:not(.gb_Oc) .gb_vb{left:3px}.gb_Jc.gb_Pc{-moz-animation:gb__nb .6s 1s both ease-in-out;animation:gb__nb .6s 1s both ease-in-out;-moz-perspective-origin:top right;perspective-origin:top right;-moz-transform:scale(1,1);transform:scale(1,1);-moz-transform-origin:top right;transform-origin:top right}.gb_Pc .gb_Lc{visibility:visible}.gb_ea .gb_b .gb_Hc{background-position:0 0;opacity:.7}.gb_ea .gb_Ic .gb_Hc{background-position:-279px -38px}.gb_ea .gb_b:hover .gb_Hc,.gb_ea .gb_b:focus .gb_Hc{opacity:.85}.gb_X .gb_b .gb_Hc{background-position:-349px -38px;opacity:1}.gb_X .gb_Ic .gb_Hc{background-position:-393px 0}.gb_ea .gb_Jc,.gb_X .gb_Jc{border:none}.gb_Nc .gb_Qc{font-size:14px;font-weight:bold;top:0;right:0}.gb_Nc .gb_b{display:inline-block;vertical-align:middle;-moz-box-sizing:border-box;box-sizing:border-box;height:30px;width:30px}.gb_Nc .gb_vb{border-bottom-color:#e5e5e5}.gb_Rc{background-color:rgba(0,0,0,.55);color:#fff;font-size:12px;font-weight:bold;line-height:20px;margin:5px;padding:0 2px;text-align:center;-moz-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:50%;border-radius:50%;height:20px;width:20px}.gb_Rc.gb_Sc{background-position:-194px -21px}.gb_Rc.gb_Tc{background-position:-194px -46px}.gb_b:hover .gb_Rc,.gb_b:focus .gb_Rc{background-color:rgba(0,0,0,.85)}#gbsfw.gb_Uc{background:#e5e5e5;border-color:#ccc}.gb_ea .gb_Rc{background-color:rgba(0,0,0,.7)}.gb_X .gb_Rc.gb_Rc,.gb_X .gb_Kc .gb_Rc.gb_Rc,.gb_X .gb_Kc .gb_b:hover .gb_Rc,.gb_X .gb_Kc .gb_b:focus .gb_Rc{background-color:#fff;color:#404040}.gb_X .gb_Rc.gb_Sc{background-position:-70px 0}.gb_X .gb_Rc.gb_Tc{background-position:-219px 0}.gb_Kc .gb_Rc.gb_Rc{background-color:#db4437;color:#fff}.gb_Kc .gb_b:hover .gb_Rc,.gb_Kc .gb_b:focus .gb_Rc{background-color:#a52714}#gb#gb a.gb_O,#gb#gb a.gb_P,#gb#gb span.gb_P{color:rgba(0,0,0,0.87);text-decoration:none}#gb#gb a.gb_P:hover,#gb#gb a.gb_P:focus{opacity:.85;text-decoration:underline}.gb_Q.gb_R{display:none;padding-left:15px;vertical-align:middle}.gb_Q.gb_R:first-child{padding-left:0}.gb_S.gb_R{display:inline-block}.gb_Q span{opacity:.55;-moz-user-select:text}.gb_T .gb_S.gb_R{flex:0 1 auto;flex:0 1 main-size;display:-webkit-flex;display:flex}.gb_U .gb_S.gb_R{display:none}.gb_Q .gb_P{display:inline-block;line-height:24px;outline:none;vertical-align:middle}.gb_S .gb_P{display:none}.gb_V .gb_S .gb_P{min-width:0}.gb_W .gb_S .gb_P{width:0!important}#gb#gb.gb_X a.gb_P,#gb#gb.gb_X span.gb_P,#gb#gb .gb_X a.gb_P,#gb#gb .gb_X span.gb_P{color:#fff}#gb#gb.gb_X span.gb_P,#gb#gb .gb_X span.gb_P{opacity:.7}.gb_N .gbqfi::before{left:-428px;top:0}.gb_Tb .gbqfb:focus .gbqfi{outline:1px dotted #fff}a.gb_Ba{border:none;color:#4285f4;cursor:default;font-weight:bold;outline:none;position:relative;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap;-moz-user-select:none}a.gb_Ba:hover:after,a.gb_Ba:focus:after{background-color:rgba(0,0,0,.12);content:'';height:100%;left:0;position:absolute;top:0;width:100%}a.gb_Ba:hover,a.gb_Ba:focus{text-decoration:none}a.gb_Ba:active{background-color:rgba(153,153,153,.4);text-decoration:none}a.gb_Ca{background-color:#4285f4;color:#fff}a.gb_Ca:active{background-color:#0043b2}.gb_Da{-moz-box-shadow:0 1px 1px rgba(0,0,0,.16);box-shadow:0 1px 1px rgba(0,0,0,.16)}.gb_Ba,.gb_Ca,.gb_Ea,.gb_Fa{display:inline-block;line-height:28px;padding:0 12px;-moz-border-radius:2px;border-radius:2px}.gb_Ea{background:#f8f8f8;border:1px solid #c6c6c6}.gb_Fa{background:#f8f8f8}.gb_Ea,#gb a.gb_Ea.gb_Ea,.gb_Fa{color:#666;cursor:default;text-decoration:none}#gb a.gb_Fa.gb_Fa{cursor:default;text-decoration:none}.gb_Fa{border:1px solid #4285f4;font-weight:bold;outline:none;background:#4285f4;background:-moz-linear-gradient(top,#4387fd,#4683ea);background:linear-gradient(top,#4387fd,#4683ea);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4387fd,endColorstr=#4683ea,GradientType=0)}#gb a.gb_Fa.gb_Fa{color:#fff}.gb_Fa:hover{-moz-box-shadow:0 1px 0 rgba(0,0,0,.15);box-shadow:0 1px 0 rgba(0,0,0,.15)}.gb_Fa:active{-moz-box-shadow:inset 0 2px 0 rgba(0,0,0,.15);box-shadow:inset 0 2px 0 rgba(0,0,0,.15);background:#3c78dc;background:-moz-linear-gradient(top,#3c7ae4,#3f76d3);background:linear-gradient(top,#3c7ae4,#3f76d3);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3c7ae4,endColorstr=#3f76d3,GradientType=0)}.gb_cc{min-width:127px;overflow:hidden;position:relative;z-index:987}.gb_dc{position:absolute;padding:0 20px 0 15px}.gb_ec .gb_dc{right:100%;margin-right:-127px}.gb_fc{display:inline-block;line-height:0;outline:none;vertical-align:middle}.gb_gc .gb_fc{position:relative;top:2px}.gb_fc .gb_hc,.gb_0a{display:block}.gb_ic{border:none;display:block;visibility:hidden}.gb_fc .gb_hc{background-position:0 -35px;height:33px;width:92px}img.gb_1a{border:0;vertical-align:middle}.gb_X .gb_fc .gb_hc{background-position:-296px 0}.gb_ea .gb_fc .gb_hc{background-position:-97px 0;opacity:.54}.gb_Df{display:inline-block;line-height:normal;position:relative;z-index:987}.gb_Jf .gb_b{background-position:-498px -35px;opacity:.55;height:30px;width:30px}.gb_Jf .gb_b:hover,.gb_Jf .gb_b:focus{opacity:.85}.gb_Jf .gb_vb{border-bottom-color:#f5f5f5}#gbsfw.gb_Kf{background:#f5f5f5;border-color:#ccc}.gb_X .gb_Jf .gb_b{background-position:-428px -35px;opacity:1}.gb_ea .gb_Jf .gb_b{background-position:-498px 0;opacity:.7}.gb_ea .gb_Jf .gb_b:hover,.gb_ea .gb_Jf .gb_b:focus{opacity:.85}.gb_Fg{color:#000;font:13px/27px Arial,sans-serif;left:0;min-width:1092px;position:absolute;top:0;-moz-user-select:-moz-none;width:100%}.gb_Mf{font:13px/27px Arial,sans-serif;position:relative;height:60px;width:100%}.gb_fb .gb_Mf{height:28px}#gba{height:60px}#gba.gb_fb{height:28px}#gba.gb_Hg{height:90px}#gba.gb_Ig{height:132px}#gba.gb_Hg.gb_fb{height:58px}.gb_Mf>.gb_R{height:60px;line-height:58px;vertical-align:middle}.gb_fb .gb_Mf>.gb_R{height:28px;line-height:26px}.gb_Mf::before{background:#e5e5e5;bottom:0;content:'';display:none;height:1px;left:0;position:absolute;right:0}.gb_Mf{background:#f1f1f1}.gb_Jg .gb_Mf{background:#fff}.gb_Jg .gb_Mf::before,.gb_fb .gb_Mf::before{display:none}.gb_ea .gb_Mf,.gb_X .gb_Mf,.gb_fb .gb_Mf{background:transparent}.gb_ea .gb_Mf::before{background:#e1e1e1;background:rgba(0,0,0,.12)}.gb_X .gb_Mf::before{background:#333;background:rgba(255,255,255,.2)}.gb_R{display:inline-block;flex:0 0 auto;flex:0 0 main-size}.gb_R.gb_Kg{float:right;order:1}.gb_Lg{white-space:nowrap}.gb_T .gb_Lg{display:-webkit-flex;display:flex}.gb_Lg,.gb_R{margin-left:0!important;margin-right:0!important}.gb_hc{background-image:url('//ssl.gstatic.com/gb/images/i1_1967ca6a.png');background-size:528px 68px}@media (min-resolution:1.25dppx),(-webkit-min-device-pixel-ratio:1.25),(min-device-pixel-ratio:1.25){.gb_hc{background-image:url('//ssl.gstatic.com/gb/images/i2_2ec824b0.png')}}.gb_nb{min-width:255px;padding-left:30px;padding-right:30px;position:relative;text-align:right;z-index:986;align-items:center;justify-content:flex-end;-moz-user-select:-moz-none}.gb_fb .gb_nb{min-width:0}.gb_nb.gb_R{flex:1 1 auto;flex:1 1 main-size}.gb_Ec{line-height:normal;position:relative;text-align:left}.gb_Ec.gb_R,.gb_oe.gb_R,.gb_gb.gb_R{flex:0 1 auto;flex:0 1 main-size}.gb_vg,.gb_wg{display:inline-block;padding:0 0 0 15px;position:relative;vertical-align:middle}.gb_oe{line-height:normal;padding-right:15px}.gb_nb .gb_oe.gb_U{padding-right:0}.gb_gb{color:#404040;line-height:30px;min-width:30px;overflow:hidden;vertical-align:middle;text-overflow:ellipsis}#gb.gb_fb.gb_fb .gb_3f,#gb.gb_fb.gb_fb .gb_Ec>.gb_wg .gb_4f{background:none;border:none;color:#36c;cursor:pointer;filter:none;font-size:11px;line-height:26px;padding:0;-moz-box-shadow:none;box-shadow:none}#gb.gb_fb.gb_X .gb_3f,#gb.gb_fb.gb_X .gb_Ec>.gb_wg .gb_4f{color:#fff}.gb_fb .gb_3f{text-transform:uppercase}.gb_nb.gb_V{padding-left:0;padding-right:29px}.gb_nb.gb_xg{max-width:400px}.gb_yg{background-clip:content-box;background-origin:content-box;opacity:.27;padding:22px;height:16px;width:16px}.gb_yg.gb_R{display:none}.gb_yg:hover,.gb_yg:focus{opacity:.55}.gb_zg{background-position:-219px -25px}.gb_Ag{background-position:-194px 0;padding-left:30px;padding-right:14px;position:absolute;right:0;top:0;z-index:990}.gb_jb:not(.gb_lb) .gb_Ag,.gb_V .gb_zg{display:inline-block}.gb_jb .gb_zg{padding-left:30px;padding-right:0;width:0}.gb_jb:not(.gb_lb) .gb_Bg{display:none}.gb_nb.gb_R.gb_V,.gb_V:not(.gb_lb) .gb_Ec{flex:0 0 auto;flex:0 0 main-size}.gb_yg,.gb_V .gb_oe,.gb_lb .gb_Ec{overflow:hidden}.gb_jb .gb_oe{padding-right:0}.gb_V .gb_Ec{padding:1px 1px 1px 0}.gb_jb .gb_Ec{width:75px}.gb_nb.gb_Cg,.gb_nb.gb_Cg .gb_zg,.gb_nb.gb_Cg .gb_zg::before,.gb_nb.gb_Cg .gb_oe,.gb_nb.gb_Cg .gb_Ec{-moz-transition:width .5s ease-in-out,min-width .5s ease-in-out,max-width .5s ease-in-out,padding .5s ease-in-out,left .5s ease-in-out;transition:width .5s ease-in-out,min-width .5s ease-in-out,max-width .5s ease-in-out,padding .5s ease-in-out,left .5s ease-in-out}.gb_T .gb_nb{min-width:0}.gb_nb.gb_W,.gb_nb.gb_W .gb_Ec,.gb_nb.gb_Dg,.gb_nb.gb_Dg .gb_Ec{min-width:0!important}.gb_nb.gb_W,.gb_nb.gb_W .gb_R{-moz-box-flex:0 0 auto!important;flex:0 0 auto!important}.gb_nb.gb_W .gb_gb{width:30px!important}.gb_Eg{margin-right:32px}.gb_cb{display:none}.gb_Mf ::-webkit-scrollbar{height:15px;width:15px}.gb_Mf ::-webkit-scrollbar-button{height:0;width:0}.gb_Mf ::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:rgba(0,0,0,.3);border:5px solid transparent;-moz-border-radius:10px;border-radius:10px;min-height:20px;min-width:20px;height:5px;width:5px}.gb_Mf ::-webkit-scrollbar-thumb:hover,.gb_Mf ::-webkit-scrollbar-thumb:active{background-color:rgba(0,0,0,.4)}#gb.gb_Og{min-width:980px}#gb.gb_Og .gb_If{min-width:0;position:static;width:0}.gb_Xc{display:none}.gb_Og .gb_Mf{background:transparent;border-bottom-color:transparent}.gb_Og .gb_Mf::before{display:none}.gb_Og.gb_Og .gb_Q{display:inline-block}.gb_Og.gb_nb .gb_oe.gb_U{padding-right:15px}.gb_T.gb_Og .gb_S.gb_R{display:-webkit-flex;display:flex}.gb_Og.gb_T #gbqf{display:block}.gb_Og #gbq{height:0;position:absolute}.gb_Og.gb_nb{z-index:987}sentinel{}#gbq .gbgt-hvr,#gbq .gbgt:focus{background-color:transparent;background-image:none}.gbqfh#gbq1{display:none}.gbxx{display:none !important}#gbq{line-height:normal;position:relative;top:0px;white-space:nowrap}#gbq{left:0;width:100%}#gbq2{top:0px;z-index:986}#gbq4{display:inline-block;max-height:29px;overflow:hidden;position:relative}.gbqfh#gbq2{z-index:985}.gbqfh#gbq2{margin:0;margin-left:0 !important;padding-top:0;position:relative;top:310px}.gbqfh #gbqf{margin:auto;min-width:534px;padding:0 !important}.gbqfh #gbqfbw{display:none}.gbqfh #gbqfbwa{display:block}.gbqfh #gbqf{max-width:572px;min-width:572px}.gbqfh .gbqfqw{border-right-width:1px}
.gbii::before{content:url(https://ssl.gstatic.com/gb/images/silhouette_27.png)}.gbip::before{content:url(https://ssl.gstatic.com/gb/images/silhouette_96.png)}@media (min-resolution:1.25dppx),(-o-min-device-pixel-ratio:5/4),(-webkit-min-device-pixel-ratio:1.25),(min-device-pixel-ratio:1.25){.gbii::before{content:url(https://ssl.gstatic.com/gb/images/silhouette_27.png)}.gbip::before{content:url(https://ssl.gstatic.com/gb/images/silhouette_96.png)}}
.gbii{background-image:url(https://ssl.gstatic.com/gb/images/silhouette_27.png)}.gbip{background-image:url(https://ssl.gstatic.com/gb/images/silhouette_96.png)}@media (min-resolution:1.25dppx),(-o-min-device-pixel-ratio:5/4),(-webkit-min-device-pixel-ratio:1.25),(min-device-pixel-ratio:1.25){.gbii{background-image:url(https://ssl.gstatic.com/gb/images/silhouette_27.png)}.gbip{background-image:url(https://ssl.gstatic.com/gb/images/silhouette_96.png)}}
</style><style id="gstyle">body{color:#000;margin:0}body{background:#fff}a.gb1,a.gb2,a.gb3,.link{color:#1a0dab !important}.ts{border-collapse:collapse}.ts td{padding:0}.g{line-height:1.2;text-align:left}.ti,.bl{display:inline}.ti{display:inline-table}#rhs_block{padding-bottom:15px}a:link,.w,#prs a:visited,#prs a:active,.q:active,.q:visited,.kl:active,.tbotu{color:#1a0dab}.mblink:visited,a:visited{color:#609}.cur,.b{font-weight:bold}.j{width:42em;font-size:82%}.s{max-width:48em}.sl{font-size:82%}.hd{position:absolute;width:1px;height:1px;top:-1000em;overflow:hidden}.f,.f a:link,.m{color:#666}.c h2{color:#666}.mslg cite{display:none}.ng{color:#dd4b39}h1,ol,ul,li{margin:0;padding:0}.g,body,html,input,.std,h1{font-size:small;font-family:arial,sans-serif}.c h2,h1{font-weight:normal}.blk a{color:#000}#nav a{display:block}#nav .i{color:#a90a08;font-weight:bold}.csb,.ss,.micon,.close_btn,.mbi{background:url(/images/nav_logo242.png) no-repeat;overflow:hidden}.csb,.ss{background-position:0 0;height:40px;display:block}.mbi{background-position:-153px -70px;display:inline-block;float:left;height:13px;margin-right:3px;margin-top:4px;width:13px}#nav td{padding:0;text-align:center}.ch{cursor:pointer}h3,.med{font-size:medium;font-weight:normal;margin:0;padding:0}#res h3{font-size:18px}.e{margin:2px 0 .75em}.slk div{padding-left:12px;text-indent:-10px}.blk{border-top:1px solid #6b90da;background:#f0f7f9}#cnt{clear:both}#res{padding-right:1em;margin:0 16px}.xsm{font-size:x-small}ol li{list-style:none}.sm li{margin:0}.gl,#foot a,.nobr{white-space:nowrap}#foot #navcnt a{color:#4285f4;font-weight:normal}#foot #navcnt .cur{color:rgba(0,0,0,0.87);font-weight:normal}.sl,.r{display:inline;font-weight:normal;margin:0}.r{font-size:medium}h4.r{font-size:small}.vshid{display:none}.gic{position:relative;overflow:hidden;z-index:0}.nwd{font-size:10px;padding:0 16px 30px 16px;text-align:center}#rhs{display:block;margin-left:712px;padding-bottom:10px;min-width:268px}#nyc{bottom:0;display:none;left:0;margin-left:663px;min-width:317px;overflow:hidden;position:fixed;right:0;visibility:visible}.mdm #nyc{margin-left:683px}.mdm #rhs{margin-left:732px}.big #nyc{margin-left:743px}.big #rhs{margin-left:792px;}body .big #subform_ctrl{margin-left:229px}.rhslink{width:68px}.rhsdw .rhslink{width:156px}.rhsimg{width:70px}.rhsimg.rhsdw{width:158px}.rhsimg.rhsn1st{margin-left:16px}#rhs .scrt.rhsvw,#rhs table.rhsvw{border:0}#nyc .rhsvw{border:0;padding-left:0;padding-right:0}#rhs .rhsvw{border:1px solid #ebebeb;padding-left:15px;padding-right:15px;position:relative;width:424px;}#nyc .rhsvw{width:424px}#center_col .rhsl4,#center_col .rhsl5,#center_col .rhsn5{display:none}#rhs .rhstc4 .rhsvw,#nyc.rhstc4 .rhsvw{width:336px}#rhs .rhstc3 .rhsvw,#nyc.rhstc3 .rhsvw{width:248px}.rhstc4 .rhsg4,.rhstc3 .rhsg4,.rhstc3 .rhsg3{background:none !important;display:none !important}.rhstc5 .rhsl5,.rhstc5 .rhsl4,.rhstc4 .rhsl4{background:none !important;display:none !important}.rhstc4 .rhsn4{background:none !important;display:none !important}.nrgt{margin-left:22px}.mslg .vsc{border:1px solid transparent;border-radius:2px;border-radius:2px;transition:opacity .2s ease;margin-top:2px;padding:3px 0 3px 5px;transition:opacity .2s ease;width:294px}.mslg>td{padding-right:6px;padding-top:4px}button.vspib{display:none}div.vspib{background:transparent;bottom:0;cursor:default;height:auto;margin:0;min-height:40px;padding-left:9px;padding-right:4px;position:absolute;right:-37px;top:-2px;width:28px;z-index:3}.nyc_open div.vspib{z-index:103}div.vspib:focus{outline:none}.vspii .vspiic{background:url(/images/nav_logo242.png);background-position:-3px -260px;width:15px;height:13px;margin-left:6px;margin-top:-7px;opacity:.3;position:absolute;top:50%;visibility:hidden}.vsh .vsc:hover .vspii .vspiic{visibility:visible}.vsh .vspib .vspii:hover .vspiic{opacity:1;visibility:visible;transition:opacity .25s ease}.vsh .vsdth .vspiic{opacity:1;visibility:visible;transition:opacity 1.5s ease}.nyc_open.vsh .vsdth .vspiic,.nyc_open.vsh .vspib .vspii:hover .vspiic{transition:0}.vspib:focus .vspiic{opacity:1;visibility:visible}.vsh .vspib:focus .vspiic{opacity:.3;visibility:hidden}.vso .vspiic,.vso .vspib:focus .vspiic{opacity:1;visibility:visible}.vspii{border:1px solid transparent;border-radius:2px;border-right:none;cursor:default;user-select:none;user-select:none}.vsh.nyc_opening .vsc:hover .vspii,.vsh.nyc_open .vsc:hover .vspii,.vso .vspii{background-color:#fafafa;border-color:#e6e6e6;height:100%}.vso .vspib{padding-right:0}.vsh.nyc_open .mslg .vsc:hover,.vsh.nyc_opening .mslg .vsc:hover{border-right-color:#ebebeb}.nyc_open #nycx{background:url(/images/nav_logo242.png) no-repeat;background-position:-140px -230px;height:11px;width:11px}.vsc{display:inline-block;position:relative;width:100%}#res h3.r{display:block;overflow:hidden;text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}#res h3.inl{display:inline;white-space:normal}em{font-weight:bold;font-style:normal}ol,ul,li{border:0;margin:0;padding:0}.g{margin-top:0;margin-bottom:26px}.ibk{display:-moz-inline-box;display:inline-block;vertical-align:top}.tsw{width:595px}#cnt{min-width:833px;margin-left:0}.mw{max-width:1197px}.big .mw{max-width:1280px}#cnt #center_col,#cnt #foot{width:632px}.gbh{top:24px}#gbar{margin-left:8px;height:20px}#guser{margin-right:8px;padding-bottom:5px !important}.tsf-p{padding-left:150px;margin-right:46px;max-width:739px}.big .tsf-p{margin-right:322px;padding-left:150px}.mbi{margin-bottom:-1px}.uc{padding-left:8px;position:relative;margin-left:128px;}.ucm{padding-bottom:5px;padding-top:5px;margin-bottom:8px}.col{float:left}#leftnavc,#center_col,#rhs{position:relative}#center_col{margin-left:138px;margin-right:264px;}.mdm #center_col{margin-left:138px;}.big #center_col{margin-left:138px;}#subform_ctrl{font-size:11px;margin-right:480px;position:relative;z-index:99}#subform_ctrl a.gl{color:#1a0dab}#center_col{clear:both}#res{border:0;margin:0;padding:0 16px;}#extrares{padding:0 16px}#ires{margin-top:6px}.micon,.close_btn{border:0}.close_btn{background-position:-138px -84px;float:right;height:14px;width:14px}.mitem{border-bottom:1px solid transparent;line-height:29px;opacity:1.0;}.mitem .kl{padding-left:16px}.mitem .kl:hover,.msel .kls:hover{color:#222;display:block}.mitem>.kl{color:#222;display:block}.msel{color:#dd4b39;cursor:pointer}.msel .kls{border-left:5px solid #dd4b39;padding-left:11px}.mitem>.kl,.msel{font-size:13px}#tbd{display:block;min-height:1px}.tbt{font-size:13px;line-height:1.2}.tbnow{white-space:nowrap}.tbos,.tbots{font-weight:bold}.tbst{margin-top:8px}#iszlt_sel.tbcontrol_vis{margin-left:0}.tbpc,.tbpo{font-size:13px}.tbpc,.tbo .tbpo{display:inline}.tbo .tbpc,.tbpo,#set_location_section{display:none}.lco #set_location_section{display:block}#cdr_opt{padding-left:8px;text-indent:0}.tbou #cdr_frm{display:none}#cdr_frm,#cdr_min,#cdr_max{color:rgb(102,102,102)}#cdr_min,#cdr_max{font-family:arial,sans-serif;width:100%}#cdr_opt label{display:block;font-weight:normal;margin-right:2px;white-space:nowrap}a:link,.w,.q:active,.q:visited{cursor:pointer}.osl a,.gl a,#tsf a,a.mblink,a.gl,a.fl,.slk a,.bc a,.flt,a.flt u,.blg a,#appbar a{text-decoration:none}.osl a:hover,.gl a:hover,#tsf a:hover,a.mblink:hover,a.gl:hover,a.fl:hover,.slk a:hover,.bc a:hover,.flt:hover,a.flt:hover u,.tbotu:hover,.blg a:hover{text-decoration:underline}#tads a,#tadsb a,#res a,#rhs a,#taw a{text-decoration:none}#brs a,.nsa,.tbt a,.tbotu:hover,#tbpi,#nycntg a:hover,.fl,.navend span,#botstuff a,.flt:hover u,.mlocsel span,#rhs .gl a,#nav a.pn{text-decoration:none}#ss-box a:hover{text-decoration:none}.osl{color:#777}#gbi,#gbg{border-color:#a2bff0 #558be3 #558be3 #a2bff0}#gbi a.gb2:hover,#gbg a.gb2:hover,.mi:hover{background-color:#558be3}#guser a.gb2:hover,.mi:hover,.mi:hover *{color:#fff !important}#guser{color:#000}#imagebox_bigimages .th{border:0}.vsc:hover .lupin,.intrlu:hover .lupin,.lupin.luhovm,#ires:hover .vsc:hover .lupin.luhovm{background-image:url(/images/mappins_red.png) !important;}#ires:hover .lupin.luhovm{background-image:url(/images/mappins_grey.png) !important;}.vsc:hover .lucir,.intrlu:hover .lucir,.lucir.luhovm,#ires:hover .vsc:hover .lucir.luhovm{background-image:url(/images/mapcircles_red.png) !important;}#ires:hover .lucir.luhovm{background-image:url(/images/mapcircles_grey.png) !important;}#foot .ftl{margin-right:12px}#foot{visibility:hidden}#fll a,#bfl a{color:#1a0dab;margin:0 12px;text-decoration:none}.stp{margin:7px 0 17px}body{color:#222}.s{color:#545454}.s .st em,.st.s.std em{color:#6a6a6a}.s a:visited em{color:#609}.s a:active em{color:#dd4b39}.sfcc{width:833px;}#tsf{width:833px;}.big .sfcc{max-width:1129px}.big #tsf{width:1109px}#topstuff .obp{padding-top:6px}.slk{margin-top:6px !important}.st{line-height:1.4;word-wrap:break-word}.kt{border-spacing:2px 0;margin-top:1px}.esw{vertical-align:text-bottom;}.cpbb,.kpbb,.kprb,.kpgb,.kpgrb,.ksb{-moz-border-radius:2px;border-radius:2px;cursor:default;font-family:arial,sans-serif;font-size:11px;font-weight:bold;height:27px;line-height:27px;margin:2px 0;min-width:54px;padding:0 8px;text-align:center;-moz-transition:all 0.218s;transition:all 0.218s,visibility 0s;-moz-user-select:none;}.ab_button{-moz-border-radius:2px;border-radius:2px;cursor:default;font-family:arial,sans-serif;font-size:11px;font-weight:bold;height:27px;line-height:27px;margin:2px 0;min-width:54px;padding:0 8px;text-align:center;-moz-transition:all 0.218s;transition:all 0.218s,visibility 0s;-moz-user-select:none;}#top_nav .ab_button{background:none;border:none;font:inherit;height:auto;margin:0;min-width:0;padding:0;width:auto}#top_nav .ab_button:hover{-moz-box-shadow:none;box-shadow:none;-moz-transition:none;transition:none}.ab_button.left{-moz-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px;border-right-color:transparent;margin-right:0}.ab_button.right{-moz-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0;margin-left:-1px}.kbtn-small{min-width:26px;width:26px}.ksb{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#f5f5f5,#f1f1f1);background-image:linear-gradient(top,#f5f5f5,#f1f1f1);border:1px solid #dcdcdc;border:1px solid rgba(0,0,0,0.1);color:#444;}.ab_button{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#f5f5f5,#f1f1f1);background-image:linear-gradient(top,#f5f5f5,#f1f1f1);border:1px solid #dcdcdc;border:1px solid rgba(0,0,0,0.1);color:#444;}a.ksb,.div.ksb{color:#444;text-decoration:none;cursor:default}a.ab_button{color:#444;text-decoration:none;cursor:default}.cpbb:hover,.kpbb:hover,.kprb:hover,.kpgb:hover,.kpgrb:hover,.ksb:hover{-moz-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);-moz-transition:all 0.0s;transition:all 0.0s}.ab_button:hover{-moz-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);-moz-transition:all 0.0s;transition:all 0.0s}#hdtb-tls:hover{-moz-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);-moz-transition:all 0.0s;transition:all 0.0s}.ksb:hover{background-color:#f8f8f8;background-image:-moz-linear-gradient(top,#f8f8f8,#f1f1f1);background-image:linear-gradient(top,#f8f8f8,#f1f1f1);border:1px solid #c6c6c6;color:#222;}.ab_button:hover{background-color:#f8f8f8;background-image:-moz-linear-gradient(top,#f8f8f8,#f1f1f1);background-image:linear-gradient(top,#f8f8f8,#f1f1f1);border:1px solid #c6c6c6;color:#222;}#hdtb-tls:hover{background-color:#f8f8f8;background-image:-moz-linear-gradient(top,#f8f8f8,#f1f1f1);background-image:linear-gradient(top,#f8f8f8,#f1f1f1);border:1px solid #c6c6c6;color:#222;}.ksb:active{background-color:#f6f6f6;background-image:-moz-linear-gradient(top,#f6f6f6,#f1f1f1);background-image:linear-gradient(top,#f6f6f6,#f1f1f1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}#hdtb-tls:active{background-color:#f6f6f6;background-image:-moz-linear-gradient(top,#f6f6f6,#f1f1f1);background-image:linear-gradient(top,#f6f6f6,#f1f1f1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);}.ksb.ksbs,.ksb.ksbs:hover{background-color:#eee;background-image:-moz-linear-gradient(top,#eee,#e0e0e0);background-image:linear-gradient(top,#eee,#e0e0e0);border:1px solid #ccc;-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);color:#222;margin:0}.ksb.sbm{height:20px;line-height:18px;min-width:35px}.ksb.sbf{height:21px;line-height:21px;min-width:35px}.ksb.mini{-moz-box-sizing:content-box;box-sizing:content-box;height:17px;line-height:17px;min-width:0}.ksb.left{-webkit-border-radius:2px 0 0 2px}.ksb.mid{-webkit-border-radius:0;margin-left:-1px}.ksb.right{-webkit-border-radius:0 2px 2px 0;margin-left:-1px}.ktf{-moz-border-radius:1px;-moz-box-sizing:content-box;background-color:#fff;border:1px solid #d9d9d9;border-top:1px solid #c0c0c0;box-sizing:content-box;color:#333;display:inline-block;height:29px;line-height:27px;padding-left:8px;vertical-align:top}.ktf:hover{-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border:1px solid #b9b9b9;border-top:1px solid #a0a0a0;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.ktf:focus{-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);border:1px solid #4d90fe;box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);outline:none}.ktf.mini{font-size:11px;height:17px;line-height:17px;padding:0 2px}.sbc,.sbm.sbc,.sbf.sbc{padding:0 2px;min-width:30px}#sbfrm_l{visibility:inherit !important}#rcnt{margin-top:3px;}#appbar,#rhscol{min-width:1100px}#top_nav{min-width:1100px}#appbar{background:white;-webkit-box-sizing:border-box;width:100%}.ab_wrp{height:57px;border-bottom:1px solid #ebebeb}#main{width:100%}#ab_name,#ab_shopping{color:#dd4b39;font:20px "Arial";margin-left:15px;position:absolute;top:17px}#ab_name a{color:#999}#ab_shopping a{color:#dd4b39}#ab_ctls{float:right;position:relative;right:29px;z-index:3;line-height:1;padding-top:28px}#sslock{background:url(images/srpr/safesearchlock_transparent.png) right top no-repeat;height:40px;position:absolute;right:0;top:0;width:260px;-moz-user-select:none;}.ab_ctl{display:inline-block;position:relative;margin-left:16px;}.ab_ctl.action-menu{margin-top:1px;vertical-align:middle}#hdtbSum .ab_ctl{vertical-align:baseline}a.ab_button,a.ab_button:visited{display:inline-block;color:#444;margin-top:1px}a.ab_button:hover{color:#222}#appbar a.ab_button:active,a.ab_button.selected,a.ab_button.selected:hover{color:#333}.ab_button:focus{outline:none}.ab_button.selected:focus{border-color:#ccc}.ab_button:hover>span.ab_icon{opacity:0.9}#ab_opt_icon{background-position:-42px -259px;margin-top:-2px;border-radius:50%;display:inline-block;padding:4px;vertical-align:middle}#ab_opt_icon:hover{color:#777}.selected #ab_opt_icon,#ab_opt_icon:active{background:rgba(0,0,0,0.1)}.ab_icon{background:url(/images/nav_logo242.png) no-repeat;display:inline-block;opacity:0.667;vertical-align:middle}.ab_dropdown{background:#fff;border:1px solid #dcdcdc;border:1px solid rgba(0,0,0,0.2);font-size:13px;padding:6px 0;position:absolute;right:0;top:32px;white-space:nowrap;z-index:3;-moz-transition:opacity 0.218s;transition:opacity 0.218s;-moz-box-shadow:0 2px 4px rgba(0,0,0,0.2);box-shadow:0 2px 4px rgba(0,0,0,0.2)}.ab_dropdown:focus,.ab_dropdownitem:focus,.ab_dropdownitem a:focus{outline:none}.ab_dropdownitem{margin:0;padding:0;-moz-user-select:none;}.ab_dropdownitem.selected{background-color:#eee}.ab_dropdownitem.checked{background-image:url(//ssl.gstatic.com/ui/v1/menu/checkmark.png);background-position:left center;background-repeat:no-repeat}.ab_dropdownitem.disabled{cursor:default;border:1px solid #f3f3f3;border:1px solid rgba(0,0,0,0.05);pointer-events:none}a.ab_dropdownitem.disabled{color:#b8b8b8}.ab_dropdownitem.active{-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.ab_arrow{background:url(//ssl.gstatic.com/ui/v1/zippy/arrow_down.png);background-position:right center;background-repeat:no-repeat;display:inline-block;height:4px;margin-left:3px;margin-top:-3px;vertical-align:middle;width:7px}.ab_dropdownlnk,.ab_dropdownlnkinfo{display:block;padding:8px 20px 8px 16px}a.ab_dropdownlnk,a.ab_dropdownlnk:visited,a.ab_dropdownlnk:hover,#appbar a.ab_dropdownlnk:active{color:#333}a.ab_dropdownlnkinfo,a.ab_dropdownlnkinfo:visited,a.ab_dropdownlnkinfo:hover,#appbar a.ab_dropdownlnkinfo:active{color:#15c}.ab_dropdownchecklist{padding-left:30px}.ab_dropdownrule{border-top:1px solid #ebebeb;margin-bottom:10px;margin-top:9px}#top_nav{-moz-user-select:none;}.ksb.mini{margin-top:0px;}.ab_tnav_wrp{height:33px}#hdtb-msb>.hdtb-mitem:first-child,.ab_tnav_wrp,#cnt #center_col,.mw #center_col{margin-left:150px}.mw #rhs{margin-left:820px}.mw #nyc{margin-left:651px}.klnav.klleft{margin-left:14px !important}.tbt{margin-left:8px;margin-bottom:28px}#tbpi.pt.pi{margin-top:-20px}#tbpi.pi{margin-top:0}.tbo #tbpi.pt,.tbo #tbpi{margin-top:-20px}#tbpi.pt{margin-top:8px}#tbpi{margin-top:0}#tbrt{margin-top:-20px}.lnsep{border-bottom:1px solid #ebebeb;margin-bottom:14px;margin-left:10px;margin-right:4px;margin-top:14px}.tbos,.tbots,.tbotu{color:#dd4b39}.tbou>a.q,#tbpi,#tbtro,.tbt label,#set_location_section a{color:#222}.th{border:1px solid #ebebeb}#resultStats{line-height:33px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#resultStats{padding-left:16px;padding-top:0;padding-bottom:0;padding-right:8px}#subform_ctrl{margin-left:149px}.big #subform_ctrl{padding-right:2px;margin-left:229px}.mdm .mitem .kl{padding-left:28px}.big .mitem .kl{padding-left:44px}.mdm .msel .kls{padding-left:23px}.big .msel .kls{padding-left:39px}.obsmo .dp0,.dp1{display:none}.obsmo .dp1{display:inline}#obsmtc a,.rscontainer a{text-decoration:none}#obsmtc a:hover .ul,.rscontainer a:hover .ul{text-decoration:underline}.authorship_attr{white-space:nowrap}.currency input[type=text]{background-color:white;border:1px solid #d9d9d9;border-top:1px solid #c0c0c0;box-sizing:border-box;color:#333;display:inline-block;height:29px;line-height:27px;padding-left:8px;vertical-align:top;}.currency input[type=text]:hover{border:1px solid #b9b9b9;border-top:1px solid #a0a0a0;box-shadow:inset 0px 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0px 1px 2px rgba(0,0,0,0.1);}.currency input[type=text]:focus{border:1px solid #4d90fe;box-shadow:inset 0px 1px 2px rgba(0,0,0,0.3);outline:none;-moz-box-shadow:inset 0px 1px 2px rgba(0,0,0,0.3);}body.vasq #hdtbSum{height:58px}body.vasq #hdtb-msb .hdtb-mitem.hdtb-msel,body.vasq #hdtb-msb .hdtb-mitem.hdtb-msel-pre{height:15px;line-height:15px;padding:28px 16px 12px}#hdtb-msb .hdtb-mitem.hdtb-imb{height:15px;line-height:15px;padding-top:28px}body.vasq .ab_tnav_wrp{height:43px}body.vasq #topabar.vasqHeight{margin-top:-44px !important}body.vasq #resultStats{line-height:43px}body.vasq .hdtb-mn-o,body.vasq .hdtb-mn-c{top:38px}.ellip{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.QHTnWc{color:#006621;white-space:nowrap}hr{border:0;border-bottom:1px solid #ebebeb;margin:0;}.c6EXBf,.mKoLob{margin-left:16px}.c6EXBf,.Lj3pJf{margin-right:16px}.KH4S3b{margin:0 -16px}.pIpgAc{padding-top:1px;margin-bottom:-1px}.KKgUze{font-size:18px;line-height:20px;padding-top:1px;margin-bottom:-1px;}.hOOcvb{color:rgba(0,0,0,.54)}.OAX6kd{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}a.fdYsqf{color:#609}.ZINbbc{background-color:#fff;border-radius:2px;margin-bottom:26px;font-size:14px;line-height:20px;box-shadow:0 2px 2px 0 rgba(0,0,0,0.16),0 0 0 1px rgba(0,0,0,0.08);margin-left:-16px;margin-right:-16px;font-family:'Roboto',arial,sans-serif}#rhs .ZINbbc{margin:6px -32px 26px 2px}#rhs .rhstc5 .ZINbbc{width:454px}#rhs .rhstc4 .ZINbbc{width:366px}#rhs .rhstc3 .ZINbbc{width:278px}.ZINbbc>*:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.ZINbbc>*:last-child{border-bottom-left-radius:2px;border-bottom-right-radius:2px}.hs54Nc,.JTuIPc{padding-top:16px;padding-bottom:16px}.zjbNbe,.JTuIPc{padding-left:16px;padding-right:16px;}</style><script nonce="KYI43wH9N6dmn39dV/OrTw==">(function(){var b={gen204:"dcl",clearcut:4};var c=[function(){google.c&&google.tick("load",b)}];google.dclc=function(a){c.length?c.push(a):a()};function d(){for(var a;a=c.shift();)a()}window.addEventListener?(document.addEventListener("DOMContentLoaded",d,!1),window.addEventListener("load",d,!1)):window.attachEvent&&window.attachEvent("onload",d);}).call(this);(function(){window.rwt=function(){return!0};}).call(this);(window['gbar']=window['gbar']||{})._CONFIG=[[[0,"www.gstatic.com","og.og2.en_US.yvx_Tot7HP4.O","co.uk","en","1",1,[3,2,".40.","","1300102,3700062,3700288,3700440,3700521","189073909","0"],"40400","mvm4WrSkN4vfU4rYucAF",0,0,"og.og2.-1cecz0khs5rfr.L.F4.O","AA2YrTulH-59-jRxgdSXRcZIBFga0YdnfA","AA2YrTthq0yXcLxg2xHdFgHuTSbV26gN0A","",2,0,200,"GBR",null,null,"1","1",0],null,0,["m;/_/scs/abc-static/_/js/k=gapi.gapi.en.qJZuTTuHcJE.O/m=__features__/am=AAE/rt=j/d=1/rs=AHpOoo8glyl83aQK4S9K5v-KraVNbM7RrQ","https://apis.google.com","","","","",null,1,"es_plusone_gc_20180314.0_p0","en"],["1","gci_91f30755d6a6b787dcc2a4062e6e9824.js","googleapis.client:plusone:gapi.iframes","","en"],null,null,null,[0.009999999776482582,"co.uk","1",[null,"","w",null,1,5184000,1,0,"",0,1,"",0,0,null,0],null,[["","","0",0,0,-1]],null,0,null,null,["5061451","google\\.(com|ru|ca|by|kz|com\\.mx|com\\.tr)$",1]],null,[0,0,0,null,"","","",""],[1,0.001000000047497451,1],[1,0.1000000014901161,2,1],[0,"",null,"",0,"There was an error loading your Marketplace apps.","You have no Marketplace apps.",0,[1,"https://www.google.co.uk/webhp?tab=ww","Search","","0 -483px",null,0],null,null,null,0,null,null,0],[1],[0,1,["lg"],1,["lat"]],[["","","","","","","","","","","","","","","","","","","","def","","","","","","","","",""],[""]],null,null,null,[30,127,1,0,60],null,null,null,null,null,[1,1],null,[1,0.1000000014901161,0,40400,1,"GBR","en","189073909.0",1,0.001000000047497451],null,[""]]];(window['gbar']=window['gbar']||{})._LDD=["in","fot"];/* _GlobalPrefix_ */
this.gbar_=this.gbar_||{};(function(_){var window=this;
    /* _Module_:_r */
    try{
        var ba,ha,ia,ja,qa,ra;_.aa="function"==typeof Object.create?Object.c
Download .txt
gitextract_vwfvhtj1/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build/
│   └── .gitignore
├── composer.json
├── phpcs.xml
├── phpunit.dist.xml
├── src/
│   ├── AdwordsResultItem.php
│   ├── AdwordsResultType.php
│   ├── AdwordsSectionResultSet.php
│   ├── Exception/
│   │   ├── GoogleCaptchaException.php
│   │   └── InvalidDOMException.php
│   ├── GoogleClient.php
│   ├── GoogleUrl.php
│   ├── GoogleUrlArchive.php
│   ├── GoogleUrlInterface.php
│   ├── GoogleUrlTrait.php
│   ├── NaturalResultType.php
│   ├── Page/
│   │   ├── GoogleCaptcha.php
│   │   ├── GoogleDom.php
│   │   ├── GoogleError.php
│   │   ├── GoogleSerp.php
│   │   └── NotFound.php
│   └── Parser/
│       ├── AbstractAdwordsParser.php
│       ├── AbstractParser.php
│       ├── Evaluated/
│       │   ├── AdwordsParser.php
│       │   ├── AdwordsSectionParser.php
│       │   ├── MobileAdwordsParser.php
│       │   ├── MobileAdwordsSectionParser.php
│       │   ├── MobileNaturalParser.php
│       │   ├── NaturalParser.php
│       │   └── Rule/
│       │       ├── Adwords/
│       │       │   ├── AdwordsItem.php
│       │       │   ├── AdwordsItemMobile.php
│       │       │   └── Shopping.php
│       │       └── Natural/
│       │           ├── AnswerBox.php
│       │           ├── Classical/
│       │           │   ├── ClassicalCardsResult.php
│       │           │   ├── ClassicalCardsResultO9g5cc.php
│       │           │   ├── ClassicalCardsResultZ1m.php
│       │           │   ├── ClassicalCardsResultZINbbc.php
│       │           │   ├── ClassicalCardsVideoResult.php
│       │           │   ├── ClassicalResult.php
│       │           │   └── ClassicalWithLargeVideo.php
│       │           ├── ComposedTopStories.php
│       │           ├── Divider.php
│       │           ├── Flight.php
│       │           ├── ImageGroup.php
│       │           ├── ImageGroupCarousel.php
│       │           ├── InTheNews.php
│       │           ├── KnowledgeCard.php
│       │           ├── Map.php
│       │           ├── MapLegacy.php
│       │           ├── MapMobile.php
│       │           ├── PeopleAlsoAsk.php
│       │           ├── SearchResultGroup.php
│       │           ├── TopStoriesCarousel.php
│       │           ├── TopStoriesVertical.php
│       │           ├── TweetsCarousel.php
│       │           ├── TweetsCarouselZ1m.php
│       │           └── VideoGroup.php
│       ├── ParserInterface.php
│       └── ParsingRuleInterface.php
├── stubs/
│   └── RelatedSearch.php
└── test/
    ├── bin/
    │   ├── ci.bash
    │   ├── phpcbf.bash
    │   ├── phpcs.bash
    │   └── test.bash
    ├── resources/
    │   ├── pages-evaluated/
    │   │   ├── 2018/
    │   │   │   ├── 03/
    │   │   │   │   ├── asian+massage.html
    │   │   │   │   ├── plumber+london.html
    │   │   │   │   ├── qui+est+homer+simpsons.html
    │   │   │   │   └── super+u+paris.html
    │   │   │   ├── 07/
    │   │   │   │   └── foo+bar(page2).html
    │   │   │   ├── 09/
    │   │   │   │   └── 65b6be0a-7619-4018-97c9-989cdec53319.html
    │   │   │   ├── 10/
    │   │   │   │   ├── agence+web+nantes.html
    │   │   │   │   └── who+is+homer+simpson.html
    │   │   │   └── 11/
    │   │   │       └── acheter+kobo.html
    │   │   ├── 2019/
    │   │   │   ├── 05/
    │   │   │   │   └── plombier+paris.html
    │   │   │   └── 07/
    │   │   │       └── cheap+video+editing+software+mac.html
    │   │   ├── adwords/
    │   │   │   └── simpsons+poster.html
    │   │   ├── alarmas+para+casa.html
    │   │   ├── captcha.html
    │   │   ├── cards-design.html
    │   │   ├── flights.html
    │   │   ├── github(with-vertical-top-stories).html
    │   │   ├── github.html
    │   │   ├── how+is+homer+simpsons.html
    │   │   ├── inde(top-stories).html
    │   │   ├── narendra+modi.html
    │   │   ├── page-with-bkgrouped-results.html
    │   │   ├── ransomware.html
    │   │   ├── shop-near-paris.html
    │   │   ├── simpsons(related).html
    │   │   ├── simpsons+donut.html
    │   │   ├── simpsons+movie+trailer.html
    │   │   ├── simpsons.html
    │   │   ├── simpsonsworld.html
    │   │   └── with-DOMText.html
    │   ├── pages-mobile/
    │   │   ├── 2017/
    │   │   │   ├── 11/
    │   │   │   │   ├── buy+pen.html
    │   │   │   │   └── donald+trump.html
    │   │   │   └── 12/
    │   │   │       ├── foo.html
    │   │   │       └── who+is+homer+simpsons.html
    │   │   ├── 2018/
    │   │   │   ├── 03/
    │   │   │   │   └── foo.html
    │   │   │   ├── 07/
    │   │   │   │   └── simpsons-episode-1.html
    │   │   │   ├── 08/
    │   │   │   │   └── new+construction+ct.html
    │   │   │   └── 11/
    │   │   │       └── 01/
    │   │   │           └── plombier+nantes.html
    │   │   ├── simpsons+donuts.html
    │   │   ├── simpsons+homer.html
    │   │   └── simpsons+world.html
    │   ├── pages-raw/
    │   │   └── simpsons.html
    │   └── simple-dom.html
    └── suites/
        ├── AdwordsResultItemTest.php
        ├── AdwordsSectionResultSetTest.php
        ├── GoogleClientTest.php
        ├── GoogleSerpTestCase.php
        ├── GoogleUrlTest.php
        ├── Page/
        │   ├── GoogleCaptchaTest.php
        │   ├── GoogleDomTest.php
        │   └── GoogleSerpTest.php
        └── Parser/
            └── Evaluated/
                ├── AdwordsParserTest.php
                ├── NaturalParserTest.php
                └── natural-parser-data/
                    ├── 2018/
                    │   ├── 03/
                    │   │   ├── asian+massage.yml
                    │   │   ├── plumber+london.yml
                    │   │   ├── qui+est+homer+simpsons.yml
                    │   │   └── super+u+paris.yml
                    │   ├── 07/
                    │   │   └── foo+bar(page2).yml
                    │   ├── 09/
                    │   │   └── 65b6be0a-7619-4018-97c9-989cdec53319.yml
                    │   ├── 10/
                    │   │   ├── agence+web+nantes.yml
                    │   │   └── who+is+homer+simpson.yml
                    │   └── 11/
                    │       └── acheter+kobo.yml
                    ├── 2019/
                    │   ├── 05/
                    │   │   └── plombier+paris.yml
                    │   └── 07/
                    │       └── cheap+video+editing+software+mac.yml
                    ├── github(with-vertical-top-stories).yml
                    ├── inde(top-stories).yml
                    ├── mobile/
                    │   ├── 2017/
                    │   │   ├── 11/
                    │   │   │   ├── buy+pen.yml
                    │   │   │   └── mobile-donald+trump.yml
                    │   │   └── 12/
                    │   │       ├── foo.yml
                    │   │       └── who+is+homer+simpsons.yml
                    │   └── 2018/
                    │       ├── 03/
                    │       │   └── foo.yml
                    │       ├── 07/
                    │       │   └── simpsons-episode-1.yml
                    │       ├── 08/
                    │       │   └── new-construction-ct.yml
                    │       └── 11/
                    │           └── 01/
                    │               └── plombier+nantes.yml
                    ├── mobile-simpsons+donuts.yml
                    ├── mobile-simpsons+homer.yml
                    ├── mobile-simpsons+world.yml
                    ├── narendra+modi.yml
                    ├── natural-cards.yml
                    ├── naturals-data-with_bkgroups.yml
                    ├── naturals-data.yml
                    ├── ransomware.yml
                    ├── simpsons+donuts.yml
                    ├── simpsons+movie+trailer.yml
                    └── simpsonsworld.yml
Download .txt
SYMBOL INDEX (262 symbols across 65 files)

FILE: src/AdwordsResultItem.php
  class AdwordsResultItem (line 12) | class AdwordsResultItem extends ProxyResult
    method __construct (line 23) | public function __construct($location, ResultDataInterface $itemData)
    method getTypes (line 29) | public function getTypes()
    method is (line 36) | public function is($types)

FILE: src/AdwordsResultType.php
  class AdwordsResultType (line 8) | class AdwordsResultType

FILE: src/AdwordsSectionResultSet.php
  class AdwordsSectionResultSet (line 15) | class AdwordsSectionResultSet extends IndexedResultSet
    method __construct (line 23) | public function __construct($location)
    method addItem (line 33) | public function addItem(ResultDataInterface $item)

FILE: src/Exception/GoogleCaptchaException.php
  class GoogleCaptchaException (line 14) | class GoogleCaptchaException extends CaptchaException
    method __construct (line 17) | public function __construct(GoogleCaptcha $captchaResponse)

FILE: src/Exception/InvalidDOMException.php
  class InvalidDOMException (line 10) | class InvalidDOMException extends Exception
    method __construct (line 13) | public function __construct($message)

FILE: src/GoogleClient.php
  class GoogleClient (line 28) | class GoogleClient
    method __construct (line 33) | public function __construct(BrowserInterface $browser = null)
    method query (line 48) | public function query(GoogleUrlInterface $googleUrl, BrowserInterface ...

FILE: src/GoogleUrl.php
  class GoogleUrl (line 11) | class GoogleUrl extends Url implements GoogleUrlInterface
    method __construct (line 24) | public function __construct(
    method build (line 37) | public static function build(
    method setLanguageRestriction (line 64) | public function setLanguageRestriction($lang)
    method setPage (line 83) | public function setPage($pageNumber)
    method setResultsPerPage (line 98) | public function setResultsPerPage($number)
    method setSearchTerm (line 123) | public function setSearchTerm($search)
    method setAutoCorrectionEnabled (line 133) | public function setAutoCorrectionEnabled($enabled)
    method setResultType (line 148) | public function setResultType($resultType)

FILE: src/GoogleUrlArchive.php
  class GoogleUrlArchive (line 15) | class GoogleUrlArchive extends UrlArchive implements GoogleUrlInterface
    method __construct (line 19) | public function __construct(
    method build (line 32) | public static function build(

FILE: src/GoogleUrlInterface.php
  type GoogleUrlInterface (line 14) | interface GoogleUrlInterface extends UrlArchiveInterface
    method getPage (line 21) | public function getPage();
    method getLanguageRestriction (line 26) | public function getLanguageRestriction();
    method getResultsPerPage (line 32) | public function getResultsPerPage();
    method getResultType (line 38) | public function getResultType();
    method getArchive (line 44) | public function getArchive();
    method getAutoCorrectionEnabled (line 50) | public function getAutoCorrectionEnabled();

FILE: src/GoogleUrlTrait.php
  type GoogleUrlTrait (line 16) | trait GoogleUrlTrait
    method getParamValue (line 19) | abstract public function getParamValue($param, $defaultValue = null);
    method buildUrl (line 20) | abstract public function buildUrl();
    method getParamRawValue (line 21) | abstract public function getParamRawValue($param, $defaultValue = null);
    method getHost (line 22) | abstract public function getHost();
    method getPath (line 23) | abstract public function getPath();
    method getScheme (line 24) | abstract public function getScheme();
    method getParams (line 25) | abstract public function getParams();
    method getHash (line 26) | abstract public function getHash();
    method getPage (line 32) | public function getPage()
    method getLanguageRestriction (line 41) | public function getLanguageRestriction()
    method getResultsPerPage (line 50) | public function getResultsPerPage()
    method getResultType (line 60) | public function getResultType()
    method getAutoCorrectionEnabled (line 69) | public function getAutoCorrectionEnabled()
    method getSearchTerm (line 78) | public function getSearchTerm()
    method getArchive (line 83) | public function getArchive()

FILE: src/NaturalResultType.php
  class NaturalResultType (line 8) | abstract class NaturalResultType

FILE: src/Page/GoogleCaptcha.php
  class GoogleCaptcha (line 13) | class GoogleCaptcha implements CaptchaResponse
    method __construct (line 25) | public function __construct(GoogleError $googleError)
    method getErrorPage (line 33) | public function getErrorPage()
    method getCaptchaType (line 39) | public function getCaptchaType()
    method getData (line 50) | public function getData()
    method getDetectedIp (line 55) | public function getDetectedIp()

FILE: src/Page/GoogleDom.php
  class GoogleDom (line 17) | class GoogleDom extends WebPage
    method __construct (line 38) | public function __construct($domString, GoogleUrlInterface $url)
    method getJsonNodeProperty (line 57) | public function getJsonNodeProperty($propertyName, \DOMNode $node)

FILE: src/Page/GoogleError.php
  class GoogleError (line 11) | class GoogleError extends WebPage
    method isCaptcha (line 17) | public function isCaptcha()

FILE: src/Page/GoogleSerp.php
  class GoogleSerp (line 16) | class GoogleSerp extends GoogleDom
    method getLocation (line 23) | public function getLocation()
    method getNaturalResults (line 38) | public function getNaturalResults()
    method getAdwordsResults (line 58) | public function getAdwordsResults()
    method getNumberOfResults (line 78) | public function getNumberOfResults()
    method javascriptIsEvaluated (line 113) | public function javascriptIsEvaluated()
    method getRelatedSearches (line 136) | public function getRelatedSearches()
    method isMobile (line 173) | public function isMobile()

FILE: src/Page/NotFound.php
  class NotFound (line 8) | class NotFound extends GoogleDom

FILE: src/Parser/AbstractAdwordsParser.php
  class AbstractAdwordsParser (line 11) | abstract class AbstractAdwordsParser implements ParserInterface
    method generateParsers (line 23) | abstract public function generateParsers();
    method getParsers (line 28) | public function getParsers()
    method parse (line 39) | public function parse(GoogleDom $googleDom)

FILE: src/Parser/AbstractParser.php
  class AbstractParser (line 12) | abstract class AbstractParser implements ParserInterface
    method generateRules (line 23) | abstract protected function generateRules();
    method getParsableItems (line 29) | abstract protected function getParsableItems(GoogleDom $googleDom);
    method getRules (line 35) | public function getRules()
    method parse (line 48) | public function parse(GoogleDom $googleDom)
    method createResultSet (line 60) | protected function createResultSet(GoogleDom $googleDom)
    method parseGroups (line 72) | protected function parseGroups(DomNodeList $elementGroups, IndexedResu...

FILE: src/Parser/Evaluated/AdwordsParser.php
  class AdwordsParser (line 12) | class AdwordsParser extends AbstractAdwordsParser
    method generateParsers (line 18) | public function generateParsers()

FILE: src/Parser/Evaluated/AdwordsSectionParser.php
  class AdwordsSectionParser (line 17) | class AdwordsSectionParser extends AbstractParser
    method __construct (line 26) | public function __construct($pathToItems, $location)
    method createResultSet (line 35) | protected function createResultSet(GoogleDom $googleDom)
    method generateRules (line 43) | protected function generateRules()
    method getParsableItems (line 54) | protected function getParsableItems(GoogleDom $googleDom)

FILE: src/Parser/Evaluated/MobileAdwordsParser.php
  class MobileAdwordsParser (line 12) | class MobileAdwordsParser extends AbstractAdwordsParser
    method generateParsers (line 18) | public function generateParsers()

FILE: src/Parser/Evaluated/MobileAdwordsSectionParser.php
  class MobileAdwordsSectionParser (line 13) | class MobileAdwordsSectionParser extends AdwordsSectionParser
    method generateRules (line 18) | protected function generateRules()

FILE: src/Parser/Evaluated/MobileNaturalParser.php
  class MobileNaturalParser (line 30) | class MobileNaturalParser extends AbstractParser
    method generateRules (line 36) | protected function generateRules()
    method getParsableItems (line 60) | protected function getParsableItems(GoogleDom $googleDom)

FILE: src/Parser/Evaluated/NaturalParser.php
  class NaturalParser (line 29) | class NaturalParser extends AbstractParser
    method generateRules (line 35) | protected function generateRules()
    method getParsableItems (line 59) | protected function getParsableItems(GoogleDom $googleDom)

FILE: src/Parser/Evaluated/Rule/Adwords/AdwordsItem.php
  class AdwordsItem (line 16) | class AdwordsItem implements ParsingRuleInterface
    method match (line 19) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 26) | public function parse(GoogleDom $googleDOM, \DomElement $node, Indexed...

FILE: src/Parser/Evaluated/Rule/Adwords/AdwordsItemMobile.php
  class AdwordsItemMobile (line 16) | class AdwordsItemMobile implements ParsingRuleInterface
    method match (line 22) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 33) | public function parse(GoogleDom $googleDOM, \DomElement $node, Indexed...

FILE: src/Parser/Evaluated/Rule/Adwords/Shopping.php
  class Shopping (line 16) | class Shopping implements ParsingRuleInterface
    method match (line 19) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 28) | public function parse(GoogleDom $googleDOM, \DomElement $node, Indexed...
    method parseItem (line 46) | public function parseItem(GoogleDom $googleDOM, \DOMNode $node)

FILE: src/Parser/Evaluated/Rule/Natural/AnswerBox.php
  class AnswerBox (line 14) | class AnswerBox implements ParsingRuleInterface
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parseNode (line 30) | protected function parseNode(GoogleDom $dom, \DOMElement $node)
    method parse (line 78) | public function parse(GoogleDom $dom, \DOMElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResult.php
  class ClassicalCardsResult (line 14) | class ClassicalCardsResult extends ClassicalResult
    method match (line 17) | public function match(GoogleDom $dom, DomElement $node)
    method isLarge (line 44) | protected function isLarge(GoogleDom $dom, \DomElement $node)

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultO9g5cc.php
  class ClassicalCardsResultO9g5cc (line 19) | class ClassicalCardsResultO9g5cc implements ParsingRuleInterface
    method match (line 22) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 34) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseNode (line 44) | protected function parseNode(GoogleDom $dom, DomNodeInterface $node)

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultZ1m.php
  class ClassicalCardsResultZ1m (line 21) | class ClassicalCardsResultZ1m implements ParsingRuleInterface
    method match (line 24) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 52) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseNode (line 62) | protected function parseNode(GoogleDom $dom, DomElement $node)

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultZINbbc.php
  class ClassicalCardsResultZINbbc (line 20) | class ClassicalCardsResultZINbbc implements ParsingRuleInterface
    method match (line 23) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 34) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseNode (line 44) | protected function parseNode(GoogleDom $dom, DomNodeInterface $node)

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsVideoResult.php
  class ClassicalCardsVideoResult (line 15) | class ClassicalCardsVideoResult extends ClassicalCardsResult
    method match (line 18) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 31) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalResult.php
  class ClassicalResult (line 17) | class ClassicalResult implements ParsingRuleInterface
    method match (line 20) | public function match(GoogleDom $dom, DomElement $node)
    method parseNode (line 30) | protected function parseNode(GoogleDom $dom, \DomElement $node)
    method parseSiteLink (line 82) | protected function parseSiteLink(GoogleDom $dom, \DomElement $node)
    method isLarge (line 116) | protected function isLarge(GoogleDom $dom, \DomElement $node)
    method parse (line 121) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/Classical/ClassicalWithLargeVideo.php
  class ClassicalWithLargeVideo (line 15) | class ClassicalWithLargeVideo implements ParsingRuleInterface
    method match (line 18) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 29) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/ComposedTopStories.php
  class ComposedTopStories (line 16) | class ComposedTopStories implements ParsingRuleInterface
    method match (line 18) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 31) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseNode (line 40) | private function parseNode(GoogleDom $dom, $node)
    method parseVerticalResults (line 59) | private function parseVerticalResults(GoogleDom $dom, DomElement $node)
    method parseCarouselResults (line 80) | private function parseCarouselResults(GoogleDom $dom, DomElement $node)

FILE: src/Parser/Evaluated/Rule/Natural/Divider.php
  class Divider (line 12) | class Divider implements \Serps\SearchEngine\Google\Parser\ParsingRuleIn...
    method match (line 15) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 25) | public function parse(GoogleDom $googleDOM, \DomElement $group, Indexe...

FILE: src/Parser/Evaluated/Rule/Natural/Flight.php
  class Flight (line 14) | class Flight implements \Serps\SearchEngine\Google\Parser\ParsingRuleInt...
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 25) | public function parse(GoogleDom $googleDOM, \DomElement $group, Indexe...

FILE: src/Parser/Evaluated/Rule/Natural/ImageGroup.php
  class ImageGroup (line 16) | class ImageGroup implements \Serps\SearchEngine\Google\Parser\ParsingRul...
    method match (line 19) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 27) | public function parse(GoogleDom $googleDOM, \DomElement $node, Indexed...
    method parseItem (line 53) | private function parseItem(GoogleDom $googleDOM, \DOMElement $imgNode)

FILE: src/Parser/Evaluated/Rule/Natural/ImageGroupCarousel.php
  class ImageGroupCarousel (line 16) | class ImageGroupCarousel implements \Serps\SearchEngine\Google\Parser\Pa...
    method match (line 19) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 27) | public function parse(GoogleDom $googleDOM, \DomElement $node, Indexed...
    method parseItem (line 59) | private function parseItem(GoogleDom $googleDOM, \DOMElement $imgNode)

FILE: src/Parser/Evaluated/Rule/Natural/InTheNews.php
  class InTheNews (line 15) | class InTheNews implements \Serps\SearchEngine\Google\Parser\ParsingRule...
    method match (line 18) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 30) | public function parse(GoogleDom $googleDOM, \DomElement $group, Indexe...
    method parseItem (line 49) | protected function parseItem(GoogleDom $googleDOM, \DomElement $node)

FILE: src/Parser/Evaluated/Rule/Natural/KnowledgeCard.php
  class KnowledgeCard (line 15) | class KnowledgeCard implements ParsingRuleInterface
    method match (line 18) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 26) | public function parse(GoogleDom $googleDOM, \DomElement $node, Indexed...

FILE: src/Parser/Evaluated/Rule/Natural/Map.php
  class Map (line 15) | class Map implements ParsingRuleInterface
    method match (line 18) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 26) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseItem (line 51) | private function parseItem($localPack, GoogleDom $dom)

FILE: src/Parser/Evaluated/Rule/Natural/MapLegacy.php
  class MapLegacy (line 21) | class MapLegacy implements ParsingRuleInterface
    method match (line 24) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 32) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseItem (line 59) | private function parseItem($localPack, GoogleDom $dom)

FILE: src/Parser/Evaluated/Rule/Natural/MapMobile.php
  class MapMobile (line 15) | class MapMobile implements ParsingRuleInterface
    method match (line 18) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 26) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...
    method parseItem (line 47) | private function parseItem($localPack, GoogleDom $dom)

FILE: src/Parser/Evaluated/Rule/Natural/PeopleAlsoAsk.php
  class PeopleAlsoAsk (line 24) | class PeopleAlsoAsk implements ParsingRuleInterface
    method match (line 27) | public function match(GoogleDom $dom, DomElement $node)
    method parse (line 39) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/SearchResultGroup.php
  class SearchResultGroup (line 15) | class SearchResultGroup implements ParsingRuleInterface
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 26) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/TopStoriesCarousel.php
  class TopStoriesCarousel (line 15) | class TopStoriesCarousel implements ParsingRuleInterface
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parseNode (line 30) | private function parseNode(GoogleDom $dom, $node)
    method parse (line 61) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/TopStoriesVertical.php
  class TopStoriesVertical (line 15) | class TopStoriesVertical implements ParsingRuleInterface
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parseNode (line 29) | private function parseNode(GoogleDom $dom, $node)
    method parse (line 60) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/TweetsCarousel.php
  class TweetsCarousel (line 14) | class TweetsCarousel implements ParsingRuleInterface
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 26) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/TweetsCarouselZ1m.php
  class TweetsCarouselZ1m (line 19) | class TweetsCarouselZ1m implements ParsingRuleInterface
    method match (line 22) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 37) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/Evaluated/Rule/Natural/VideoGroup.php
  class VideoGroup (line 18) | class VideoGroup implements ParsingRuleInterface
    method match (line 21) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node)
    method parse (line 29) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: src/Parser/ParserInterface.php
  type ParserInterface (line 11) | interface ParserInterface
    method parse (line 18) | public function parse(GoogleDom $googleDom);

FILE: src/Parser/ParsingRuleInterface.php
  type ParsingRuleInterface (line 11) | interface ParsingRuleInterface
    method match (line 17) | public function match(GoogleDom $dom, \Serps\Core\Dom\DomElement $node);
    method parse (line 18) | public function parse(GoogleDom $dom, \DomElement $node, IndexedResult...

FILE: stubs/RelatedSearch.php
  class RelatedSearch (line 9) | class RelatedSearch

FILE: test/suites/AdwordsResultItemTest.php
  class AdwordsResultItemTest (line 14) | class AdwordsResultItemTest extends \PHPUnit_Framework_TestCase
    method testIs (line 17) | public function testIs()
    method testGetTypes (line 27) | public function testGetTypes()

FILE: test/suites/AdwordsSectionResultSetTest.php
  class AdwordsSectionResultSetTest (line 15) | class AdwordsSectionResultSetTest extends \PHPUnit_Framework_TestCase
    method testAddItem (line 18) | public function testAddItem()

FILE: test/suites/GoogleClientTest.php
  class GoogleClientTest (line 25) | class GoogleClientTest extends \PHPUnit_Framework_TestCase
    method testValidDom (line 28) | public function testValidDom()
    method testInvalidHttpResponse (line 51) | public function testInvalidHttpResponse()
    method testCaptchaDom (line 77) | public function testCaptchaDom()

FILE: test/suites/GoogleSerpTestCase.php
  class GoogleSerpTestCase (line 14) | class GoogleSerpTestCase extends \PHPUnit_Framework_TestCase
    method assertResultHasTypes (line 17) | public function assertResultHasTypes(
    method assertResultDoesNotHaveTypes (line 53) | public function assertResultDoesNotHaveTypes(array $types, ResultDataI...
    method assertResultHasData (line 61) | public function assertResultHasData(array $dataArray, $result, $curren...
    method assertResultDataCount (line 99) | public function assertResultDataCount(array $dataArray, $result)
    method assertResultHasDataMedia (line 116) | public function assertResultHasDataMedia(array $dataArray, $result)

FILE: test/suites/GoogleUrlTest.php
  class GoogleUrlTest (line 17) | class GoogleUrlTest extends \PHPUnit_Framework_TestCase
    method testConstruct (line 20) | public function testConstruct()
    method testGetArchive (line 27) | public function testGetArchive()
    method testLanguageRestriction (line 35) | public function testLanguageRestriction()
    method testPage (line 49) | public function testPage()
    method testResultsPerPage (line 67) | public function testResultsPerPage()
    method testSearchTerm (line 89) | public function testSearchTerm()
    method testResultType (line 99) | public function testResultType()

FILE: test/suites/Page/GoogleCaptchaTest.php
  class GoogleCaptchaTest (line 16) | class GoogleCaptchaTest extends \PHPUnit_Framework_TestCase
    method testCaptcha (line 19) | public function testCaptcha()

FILE: test/suites/Page/GoogleDomTest.php
  class GoogleDomTest (line 15) | class GoogleDomTest extends \PHPUnit_Framework_TestCase
    method getDom (line 21) | public function getDom()
    method testGetXPath (line 27) | public function testGetXPath()
    method testGetDom (line 33) | public function testGetDom()
    method testXPathQuery (line 39) | public function testXPathQuery()
    method testCssQuery (line 59) | public function testCssQuery()
    method testGetUrl (line 71) | public function testGetUrl()
    method testGetJsonNodeProperty (line 77) | public function testGetJsonNodeProperty()

FILE: test/suites/Page/GoogleSerpTest.php
  class GoogleSerpTest (line 17) | class GoogleSerpTest extends \PHPUnit_Framework_TestCase
    method getDomJavascript (line 23) | public function getDomJavascript()
    method getDomNoJavascript (line 31) | public function getDomNoJavascript()
    method testGetNumberOfResults (line 37) | public function testGetNumberOfResults()
    method testGetLocation (line 47) | public function testGetLocation()
    method testGetNaturalResults (line 52) | public function testGetNaturalResults()
    method testGetAdwordsResults (line 67) | public function testGetAdwordsResults()
    method testJavascriptEvaluated (line 78) | public function testJavascriptEvaluated()
    method testRelatedSearches (line 84) | public function testRelatedSearches()

FILE: test/suites/Parser/Evaluated/AdwordsParserTest.php
  class AdwordsParserTest (line 33) | class AdwordsParserTest extends \PHPUnit_Framework_TestCase
    method testParserTopAndBottom (line 35) | public function testParserTopAndBottom()

FILE: test/suites/Parser/Evaluated/NaturalParserTest.php
  class NaturalParserTest (line 57) | class NaturalParserTest extends GoogleSerpTestCase
    method serpProvider (line 60) | public function serpProvider()
    method testSerps (line 78) | public function testSerps($file)
    method testResultWithMap (line 170) | public function testResultWithMap()
    method testLargeResult (line 225) | public function testLargeResult()
    method testNidGroup (line 261) | public function testNidGroup()
    method testFlights (line 296) | public function testFlights()
    method testAnswerBox (line 327) | public function testAnswerBox()
    method testResultPosition (line 369) | public function testResultPosition()
    method testResultWithDomText (line 395) | public function testResultWithDomText()
Copy disabled (too large) Download .json
Condensed preview — 156 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (16,573K chars).
[
  {
    "path": ".editorconfig",
    "chars": 415,
    "preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.o"
  },
  {
    "path": ".gitattributes",
    "chars": 379,
    "preview": "/build              export-ignore\n/script             export-ignore\n/test               export-ignore\n.codeclimate.yml  "
  },
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": "composer.lock\n/vendor\n.idea\n"
  },
  {
    "path": ".travis.yml",
    "chars": 1376,
    "preview": "language: php\n\ndist: trusty\nsudo: false\n\nmatrix:\n  include:\n  - php: 5.5\n    env: PROCESS_CODECLIMATE=true\n  - php: 5.6\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 6905,
    "preview": "# CHANGELOG\n\n## 0.4.7\n\n*2018-18-05*\n\n* Bug fix: \n    * Fixed title for adwords results\n\n## 0.4.6\n\n*2018-30-10*\n\n* Featur"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1502,
    "preview": "CONTRIBUTING\n============\n\nAny contribution is welcome.\n\n\nIssue\n-----\n\nIf you encounter an issue with the library you ar"
  },
  {
    "path": "LICENSE",
    "chars": 284,
    "preview": "Copyright (c) 2015-today, Soufiane GHZAL <sghzal@gmail.com> and contributors\n\nUsage of the works is permitted provided t"
  },
  {
    "path": "README.md",
    "chars": 1760,
    "preview": "SERPS - Search Engine: Google\n=============================\n\n[![Build Status](https://travis-ci.org/serp-spider/search-e"
  },
  {
    "path": "build/.gitignore",
    "chars": 14,
    "preview": "*\n!.gitignore\n"
  },
  {
    "path": "composer.json",
    "chars": 1265,
    "preview": "{\n\n  \"name\": \"serps/search-engine-google\",\n  \"description\": \"Google Rules and client for SERPS\",\n  \"type\": \"library\",\n  "
  },
  {
    "path": "phpcs.xml",
    "chars": 1197,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ruleset name=\"gsouf\">\n\n    <file>./src/</file>\n    <file>./test/suites</file>\n\n "
  },
  {
    "path": "phpunit.dist.xml",
    "chars": 1191,
    "preview": "<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         forceCoversAnnotation=\"true\"\n         b"
  },
  {
    "path": "src/AdwordsResultItem.php",
    "chars": 918,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\Core\\Serp\\BaseResult;\nuse Serps\\C"
  },
  {
    "path": "src/AdwordsResultType.php",
    "chars": 391,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nclass AdwordsResultType\n{\n\n    const SECTIO"
  },
  {
    "path": "src/AdwordsSectionResultSet.php",
    "chars": 779,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\Core\\Serp\\ItemPosition;\nuse Serps"
  },
  {
    "path": "src/Exception/GoogleCaptchaException.php",
    "chars": 415,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Exception;\n\nuse Serps\\Exception\\RequestError\\"
  },
  {
    "path": "src/Exception/InvalidDOMException.php",
    "chars": 319,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Exception;\n\nuse Serps\\Exception;\n\nclass Inval"
  },
  {
    "path": "src/GoogleClient.php",
    "chars": 3080,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\Core\\Browser\\BrowserInterface;\nus"
  },
  {
    "path": "src/GoogleUrl.php",
    "chars": 4014,
    "preview": "<?php\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\SearchEngine\\Google\\GoogleUrlTrait;\nuse Serps\\Core\\Url;\n\n/**\n * A"
  },
  {
    "path": "src/GoogleUrlArchive.php",
    "chars": 1091,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\Core\\UrlArchive;\nuse Serps\\Search"
  },
  {
    "path": "src/GoogleUrlInterface.php",
    "chars": 1215,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\Core\\Url\\UrlArchiveInterface;\n\n/*"
  },
  {
    "path": "src/GoogleUrlTrait.php",
    "chars": 2419,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nuse Serps\\Core\\Url\\QueryParam;\nuse Serps\\Se"
  },
  {
    "path": "src/NaturalResultType.php",
    "chars": 1108,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google;\n\nabstract class NaturalResultType\n{\n\n    con"
  },
  {
    "path": "src/Page/GoogleCaptcha.php",
    "chars": 2104,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Page;\n\nuse Serps\\Core\\Captcha\\CaptchaResponse"
  },
  {
    "path": "src/Page/GoogleDom.php",
    "chars": 1833,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Page;\n\nuse Psr\\Http\\Message\\ResponseInterface"
  },
  {
    "path": "src/Page/GoogleError.php",
    "chars": 366,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Page;\n\nuse Serps\\Core\\Dom\\WebPage;\nuse Serps\\"
  },
  {
    "path": "src/Page/GoogleSerp.php",
    "chars": 5413,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Page;\n\nuse Serps\\Exception;\nuse Serps\\SearchE"
  },
  {
    "path": "src/Page/NotFound.php",
    "chars": 120,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Page;\n\nclass NotFound extends GoogleDom\n{\n\n}\n"
  },
  {
    "path": "src/Parser/AbstractAdwordsParser.php",
    "chars": 1069,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser;\n\nuse Serps\\Core\\Serp\\CompositeResultS"
  },
  {
    "path": "src/Parser/AbstractParser.php",
    "chars": 2846,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser;\n\nuse Serps\\Core\\Dom\\DomNodeList;\nuse "
  },
  {
    "path": "src/Parser/Evaluated/AdwordsParser.php",
    "chars": 799,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchEngine\\Goo"
  },
  {
    "path": "src/Parser/Evaluated/AdwordsSectionParser.php",
    "chars": 1258,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchEngine\\Goo"
  },
  {
    "path": "src/Parser/Evaluated/MobileAdwordsParser.php",
    "chars": 781,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchEngine\\Goo"
  },
  {
    "path": "src/Parser/Evaluated/MobileAdwordsSectionParser.php",
    "chars": 443,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchEngine\\Goo"
  },
  {
    "path": "src/Parser/Evaluated/MobileNaturalParser.php",
    "chars": 2740,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchEngine\\Goo"
  },
  {
    "path": "src/Parser/Evaluated/NaturalParser.php",
    "chars": 2221,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchEngine\\Goo"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Adwords/AdwordsItem.php",
    "chars": 2635,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Adwords;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Adwords/AdwordsItemMobile.php",
    "chars": 2075,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Adwords;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Adwords/Shopping.php",
    "chars": 3377,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Adwords;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/AnswerBox.php",
    "chars": 2767,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Sea"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResult.php",
    "chars": 1409,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultO9g5cc.php",
    "chars": 2585,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultZ1m.php",
    "chars": 3229,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsResultZINbbc.php",
    "chars": 2576,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalCardsVideoResult.php",
    "chars": 1982,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalResult.php",
    "chars": 5408,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Classical/ClassicalWithLargeVideo.php",
    "chars": 2086,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural\\Classical;\n\nuse"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/ComposedTopStories.php",
    "chars": 3177,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Divider.php",
    "chars": 776,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Flight.php",
    "chars": 838,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/ImageGroup.php",
    "chars": 2898,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/ImageGroupCarousel.php",
    "chars": 2970,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/InTheNews.php",
    "chars": 2252,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/KnowledgeCard.php",
    "chars": 1594,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/Map.php",
    "chars": 4718,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/MapLegacy.php",
    "chars": 4197,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/MapMobile.php",
    "chars": 3207,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/PeopleAlsoAsk.php",
    "chars": 1922,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/SearchResultGroup.php",
    "chars": 738,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/TopStoriesCarousel.php",
    "chars": 2222,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/TopStoriesVertical.php",
    "chars": 2115,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/TweetsCarousel.php",
    "chars": 1372,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Sea"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/TweetsCarouselZ1m.php",
    "chars": 2417,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Exc"
  },
  {
    "path": "src/Parser/Evaluated/Rule/Natural/VideoGroup.php",
    "chars": 1991,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser\\Evaluated\\Rule\\Natural;\n\nuse Serps\\Cor"
  },
  {
    "path": "src/Parser/ParserInterface.php",
    "chars": 336,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser;\n\nuse Serps\\Core\\Serp\\ResultSetInterfa"
  },
  {
    "path": "src/Parser/ParsingRuleInterface.php",
    "chars": 472,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\SearchEngine\\Google\\Parser;\n\nuse Serps\\SearchEngine\\Google\\Page\\G"
  },
  {
    "path": "stubs/RelatedSearch.php",
    "chars": 250,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Stubs;\n\n\nclass RelatedSearch\n{\n\n    /**\n     * @var string url of"
  },
  {
    "path": "test/bin/ci.bash",
    "chars": 962,
    "preview": "#!/bin/bash\n\nset -e\n\nSCRIPTFILE=$(readlink -f \"$0\")\nSCRIPTDIR=$(dirname \"$SCRIPTFILE\")\n\n\necho -e \"\\e[34m\"\necho \"========"
  },
  {
    "path": "test/bin/phpcbf.bash",
    "chars": 186,
    "preview": "#!/bin/bash\n\nSCRIPTFILE=$(readlink -f \"$0\")\nSCRIPTDIR=$(dirname \"$SCRIPTFILE\")\n\ncd $SCRIPTDIR/../.. && $SCRIPTDIR/../../"
  },
  {
    "path": "test/bin/phpcs.bash",
    "chars": 257,
    "preview": "#!/bin/bash\n\nSCRIPTFILE=$(readlink -f \"$0\")\nSCRIPTDIR=$(dirname \"$SCRIPTFILE\")\n\n\nif [ -n \"$1\" ]; then report=$1; else re"
  },
  {
    "path": "test/bin/test.bash",
    "chars": 229,
    "preview": "#!/bin/bash\n\nSCRIPTFILE=$(readlink -f \"$0\")\nSCRIPTDIR=$(dirname \"$SCRIPTFILE\")\n\n\nif [ -n \"$1\" ]; then filter=$1; else fi"
  },
  {
    "path": "test/resources/pages-evaluated/2018/03/asian+massage.html",
    "chars": 352612,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-GB\"><h"
  },
  {
    "path": "test/resources/pages-evaluated/2018/03/plumber+london.html",
    "chars": 423847,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images"
  },
  {
    "path": "test/resources/pages-evaluated/2018/03/qui+est+homer+simpsons.html",
    "chars": 489286,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images"
  },
  {
    "path": "test/resources/pages-evaluated/2018/03/super+u+paris.html",
    "chars": 408600,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/"
  },
  {
    "path": "test/resources/pages-evaluated/2018/07/foo+bar(page2).html",
    "chars": 314994,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/"
  },
  {
    "path": "test/resources/pages-evaluated/2018/09/65b6be0a-7619-4018-97c9-989cdec53319.html",
    "chars": 619090,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"de\"><head"
  },
  {
    "path": "test/resources/pages-evaluated/2018/10/agence+web+nantes.html",
    "chars": 384372,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/"
  },
  {
    "path": "test/resources/pages-evaluated/2018/10/who+is+homer+simpson.html",
    "chars": 543283,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/"
  },
  {
    "path": "test/resources/pages-evaluated/2018/11/acheter+kobo.html",
    "chars": 489020,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta charset=\"UTF-8\"><"
  },
  {
    "path": "test/resources/pages-evaluated/2019/05/plombier+paris.html",
    "chars": 421084,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images"
  },
  {
    "path": "test/resources/pages-evaluated/2019/07/cheap+video+editing+software+mac.html",
    "chars": 642718,
    "preview": "\n<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en\"><hea"
  },
  {
    "path": "test/resources/pages-evaluated/adwords/simpsons+poster.html",
    "chars": 539910,
    "preview": "<html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"en-AU\"><head><meta content=\"/images/branding/googleg/1x/go"
  },
  {
    "path": "test/resources/pages-evaluated/alarmas+para+casa.html",
    "chars": 327190,
    "preview": "<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"es\"><head><meta content=\"/images/branding/google"
  },
  {
    "path": "test/resources/pages-evaluated/captcha.html",
    "chars": 2955,
    "preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n<head><meta http-equiv=\"content-type\" content=\"te"
  },
  {
    "path": "test/resources/pages-evaluated/cards-design.html",
    "chars": 396074,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"nl\"><head"
  },
  {
    "path": "test/resources/pages-evaluated/flights.html",
    "chars": 317801,
    "preview": "<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/branding/google"
  },
  {
    "path": "test/resources/pages-evaluated/github(with-vertical-top-stories).html",
    "chars": 359987,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head"
  },
  {
    "path": "test/resources/pages-evaluated/github.html",
    "chars": 311918,
    "preview": "<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/branding/google"
  },
  {
    "path": "test/resources/pages-evaluated/how+is+homer+simpsons.html",
    "chars": 402035,
    "preview": "<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-GB\"><head><meta content=\"/images/branding/goo"
  },
  {
    "path": "test/resources/pages-evaluated/inde(top-stories).html",
    "chars": 557007,
    "preview": "<!DOCTYPE html>\n<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images"
  },
  {
    "path": "test/resources/pages-evaluated/narendra+modi.html",
    "chars": 440585,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-GB\"><head><meta content=\"/ima"
  },
  {
    "path": "test/resources/pages-evaluated/page-with-bkgrouped-results.html",
    "chars": 414401,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"ru\"><head"
  },
  {
    "path": "test/resources/pages-evaluated/ransomware.html",
    "chars": 370495,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-GB\"><head><meta content=\"/ima"
  },
  {
    "path": "test/resources/pages-evaluated/shop-near-paris.html",
    "chars": 382010,
    "preview": "<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-FR\"><head><meta content=\"/images/branding/goo"
  },
  {
    "path": "test/resources/pages-evaluated/simpsons(related).html",
    "chars": 628060,
    "preview": "<!DOCTYPE html>\n<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-FR\"><head><meta content=\"/ima"
  },
  {
    "path": "test/resources/pages-evaluated/simpsons+donut.html",
    "chars": 496811,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-AU\"><head><meta content=\"/imag"
  },
  {
    "path": "test/resources/pages-evaluated/simpsons+movie+trailer.html",
    "chars": 289803,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images/"
  },
  {
    "path": "test/resources/pages-evaluated/simpsons.html",
    "chars": 612449,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-FR\"><head><meta content=\"/imag"
  },
  {
    "path": "test/resources/pages-evaluated/simpsonsworld.html",
    "chars": 310834,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"/images"
  },
  {
    "path": "test/resources/pages-evaluated/with-DOMText.html",
    "chars": 299694,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-CA\"><h"
  },
  {
    "path": "test/resources/pages-mobile/2017/11/buy+pen.html",
    "chars": 200900,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-NZ\"><h"
  },
  {
    "path": "test/resources/pages-mobile/2017/11/donald+trump.html",
    "chars": 372928,
    "preview": "\n<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en\"><hea"
  },
  {
    "path": "test/resources/pages-mobile/2017/12/foo.html",
    "chars": 327496,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta charset=\"UTF-8\">"
  },
  {
    "path": "test/resources/pages-mobile/2017/12/who+is+homer+simpsons.html",
    "chars": 329336,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en-FR\"><head><meta charset=\"UTF-"
  },
  {
    "path": "test/resources/pages-mobile/2018/03/foo.html",
    "chars": 441668,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta charset=\"UTF-8\">"
  },
  {
    "path": "test/resources/pages-mobile/2018/07/simpsons-episode-1.html",
    "chars": 220921,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta charset=\"UTF-8\">"
  },
  {
    "path": "test/resources/pages-mobile/2018/08/new+construction+ct.html",
    "chars": 132951,
    "preview": "\n<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"en\"><hea"
  },
  {
    "path": "test/resources/pages-mobile/2018/11/01/plombier+nantes.html",
    "chars": 352340,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta charset=\"UTF-8\"><"
  },
  {
    "path": "test/resources/pages-mobile/simpsons+donuts.html",
    "chars": 268864,
    "preview": "\n<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta charset=\"UTF-8\">"
  },
  {
    "path": "test/resources/pages-mobile/simpsons+homer.html",
    "chars": 442584,
    "preview": "<!DOCTYPE html>\n<html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta http-equiv=\"cont"
  },
  {
    "path": "test/resources/pages-mobile/simpsons+world.html",
    "chars": 187921,
    "preview": "<!DOCTYPE html>\n<?xml encoding=\"UTF-8\"><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head"
  },
  {
    "path": "test/resources/pages-raw/simpsons.html",
    "chars": 91824,
    "preview": "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/SearchResultsPage\" lang=\"fr\"><head><meta content=\"text/htm"
  },
  {
    "path": "test/resources/simple-dom.html",
    "chars": 288,
    "preview": "<html>\n    <body>\n        <div class=\"foo bar\">\n            <span class=\"foo\">foo bar - foo span</span>\n            <spa"
  },
  {
    "path": "test/suites/AdwordsResultItemTest.php",
    "chars": 860,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google;\n\nuse Serps\\Core\\Serp\\BaseResult;\nus"
  },
  {
    "path": "test/suites/AdwordsSectionResultSetTest.php",
    "chars": 725,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google;\n\nuse Serps\\Core\\Serp\\BaseResult;\nus"
  },
  {
    "path": "test/suites/GoogleClientTest.php",
    "chars": 3986,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google;\n\nuse \\InvalidArgumentException;\nuse"
  },
  {
    "path": "test/suites/GoogleSerpTestCase.php",
    "chars": 4997,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\SearchEngine\\Google;\n\nuse Serps\\Core\\Media\\File;\nuse Serps\\C"
  },
  {
    "path": "test/suites/GoogleUrlTest.php",
    "chars": 4113,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google;\n\nuse Psr\\Http\\Message\\RequestInterf"
  },
  {
    "path": "test/suites/Page/GoogleCaptchaTest.php",
    "chars": 1046,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google\\Page;\n\nuse Serps\\SearchEngine\\Google"
  },
  {
    "path": "test/suites/Page/GoogleDomTest.php",
    "chars": 3446,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google\\Page;\n\nuse Serps\\Core\\Http\\Proxy;\nus"
  },
  {
    "path": "test/suites/Page/GoogleSerpTest.php",
    "chars": 3944,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google\\Page;\n\nuse Serps\\Core\\Serp\\Composite"
  },
  {
    "path": "test/suites/Parser/Evaluated/AdwordsParserTest.php",
    "chars": 4657,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchE"
  },
  {
    "path": "test/suites/Parser/Evaluated/NaturalParserTest.php",
    "chars": 17439,
    "preview": "<?php\n/**\n * @license see LICENSE\n */\n\nnamespace Serps\\Test\\TDD\\SearchEngine\\Google\\Parser\\Evaluated;\n\nuse Serps\\SearchE"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/03/asian+massage.yml",
    "chars": 927,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=asian+massage\nfile: test/resources/pages-evaluated/2018/03/asi"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/03/plumber+london.yml",
    "chars": 1392,
    "preview": "# image-group recipes\nurl: https://www.google.co.uk/search?q=plumber+london\nfile: test/resources/pages-evaluated/2018/03"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/03/qui+est+homer+simpsons.yml",
    "chars": 2036,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=qui+est+homer+simpsons\nfile: test/resources/pages-evaluated/20"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/03/super+u+paris.yml",
    "chars": 879,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=super+u+paris\nfile: test/resources/pages-evaluated/2018/03/sup"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/07/foo+bar(page2).yml",
    "chars": 1709,
    "preview": "url: https://www.google.com.au/search?q=foo+bar\nfile: test/resources/pages-evaluated/2018/07/foo+bar(page2).html\n\ntest-m"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/09/65b6be0a-7619-4018-97c9-989cdec53319.yml",
    "chars": 4699,
    "preview": "url: https://www.google.de/search?q=mobile+device+management\nfile: test/resources/pages-evaluated/2018/09/65b6be0a-7619-"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/10/agence+web+nantes.yml",
    "chars": 921,
    "preview": "url: https://www.google.co.uk/search?q=agence+web+nantes\nfile: test/resources/pages-evaluated/2018/10/agence+web+nantes."
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/10/who+is+homer+simpson.yml",
    "chars": 1246,
    "preview": "url: https://www.google.co.uk/search?q=who+is+homer+simpson\nfile: test/resources/pages-evaluated/2018/10/who+is+homer+si"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2018/11/acheter+kobo.yml",
    "chars": 3317,
    "preview": "url: https://www.google.co.uk/search?q=acheter+kobo\nfile: test/resources/pages-evaluated/2018/11/acheter+kobo.html\n\ntest"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2019/05/plombier+paris.yml",
    "chars": 1080,
    "preview": "# image-group recipes\nurl: https://www.google.com/search?q=plombier+paris\nfile: test/resources/pages-evaluated/2019/05/p"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/2019/07/cheap+video+editing+software+mac.yml",
    "chars": 2108,
    "preview": "url: https://www.google.co.uk/search?q=cheap+video+editing+software+mac+nantes\nfile: test/resources/pages-evaluated/2019"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/github(with-vertical-top-stories).yml",
    "chars": 947,
    "preview": "url: https://www.google.com.au/search?q=simpsons+donut\nfile: test/resources/pages-evaluated/github(with-vertical-top-sto"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/inde(top-stories).yml",
    "chars": 1171,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=inde&hl=fr_FR\nfile: test/resources/pages-evaluated/inde(top-st"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2017/11/buy+pen.yml",
    "chars": 1829,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=buy+pen\nfile: test/resources/pages-mobile/2017/11/buy+pen.html"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2017/11/mobile-donald+trump.yml",
    "chars": 2185,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=simpsons+world\nfile: test/resources/pages-mobile/2017/11/donal"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2017/12/foo.yml",
    "chars": 2021,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=foo\nfile: test/resources/pages-mobile/2017/12/foo.html\n\ntest-m"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2017/12/who+is+homer+simpsons.yml",
    "chars": 1000,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=who+is+homer+simpsons\nfile: test/resources/pages-mobile/2017/1"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2018/03/foo.yml",
    "chars": 2436,
    "preview": "# image-group recipes\nurl: https://www.google.ca/search?q=foo\nfile: test/resources/pages-mobile/2018/03/foo.html\n\ntest-m"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2018/07/simpsons-episode-1.yml",
    "chars": 2020,
    "preview": "# image-group recipes\nurl: https://www.google.ca/search?q=simpsons+episode+1\nfile: test/resources/pages-mobile/2018/07/s"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2018/08/new-construction-ct.yml",
    "chars": 1478,
    "preview": "# image-group recipes\nurl: https://www.google.ca/search?q=new+construction+ct\nfile: test/resources/pages-mobile/2018/08/"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile/2018/11/01/plombier+nantes.yml",
    "chars": 5196,
    "preview": "url: https://www.google.co.uk/search?q=plombier+nantes\nfile: test/resources/pages-mobile/2018/11/01/plombier+nantes.html"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile-simpsons+donuts.yml",
    "chars": 2556,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=simpsons+donuts\nfile: test/resources/pages-mobile/simpsons+don"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile-simpsons+homer.yml",
    "chars": 1698,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=simpsons+movie+trailer\nfile: test/resources/pages-mobile/simps"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/mobile-simpsons+world.yml",
    "chars": 1271,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=simpsons+world\nfile: test/resources/pages-mobile/simpsons+worl"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/narendra+modi.yml",
    "chars": 687,
    "preview": "url: https://www.google.co.uk/search?q=narendra+modi&cad=h\nfile: test/resources/pages-evaluated/narendra+modi.html\nresul"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/natural-cards.yml",
    "chars": 2012,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=simpsons&hl=en_US\nfile: test/resources/pages-evaluated/cards-d"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/naturals-data-with_bkgroups.yml",
    "chars": 528,
    "preview": "url: https://www.google.fr/search?q=titley+großbritannien+hotels&hl=de_DE\nfile: test/resources/pages-evaluated/page-with"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/naturals-data.yml",
    "chars": 998,
    "preview": "url: https://www.google.fr/search?q=simpsons&hl=en_US\nfile: test/resources/pages-evaluated/simpsons.html\n\ntest-methods:\n"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/ransomware.yml",
    "chars": 895,
    "preview": "# testing null user (see TWEETS_CAROUSEL)\n\nurl: https://www.google.co.uk/search?q=ransomware\nfile: test/resources/pages-"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/simpsons+donuts.yml",
    "chars": 1300,
    "preview": "# image-group recipes\nurl: https://www.google.com.au/search?q=simpsons+donut\nfile: test/resources/pages-evaluated/simpso"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/simpsons+movie+trailer.yml",
    "chars": 870,
    "preview": "# image-group recipes\nurl: https://www.google.fr/search?q=simpsons+movie+trailer\nfile: test/resources/pages-evaluated/si"
  },
  {
    "path": "test/suites/Parser/Evaluated/natural-parser-data/simpsonsworld.yml",
    "chars": 1561,
    "preview": "url: https://www.google.fr/search?q=simpsonsworld\nfile: test/resources/pages-evaluated/simpsonsworld.html\nresults:\n\n\n  -"
  }
]

About this extraction

This page contains the full source code of the serp-spider/search-engine-google GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 156 files (15.4 MB), approximately 4.0M tokens, and a symbol index with 262 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!