[
  {
    "path": ".circleci/config.yml",
    "content": "# Clojure CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-clojure/ for more details\n#\nversion: 2.1\n\nworkflows:\n  build-deploy:\n    jobs:\n      - build:\n          filters:\n            tags:\n              only: /.*/\n\n      - deploy:\n          requires:\n            - build\n          filters:\n            tags:\n              only: /Release-.*/\n          context:\n            - CLOJARS_DEPLOY\n\njobs:\n  build:\n    docker:\n      # specify the version you desire here\n      - image: cimg/clojure:1.11.1-browsers\n\n      # Specify service dependencies here if necessary\n      # CircleCI maintains a library of pre-built images\n      # documented at https://circleci.com/docs/2.0/circleci-images/\n      # - image: circleci/postgres:9.4\n\n    working_directory: ~/repo\n\n    environment:\n      LEIN_ROOT: \"true\"\n      # Customize the JVM maximum heap limit\n      JVM_OPTS: -Xmx3200m\n\n    steps:\n      - checkout\n\n      # Download and cache dependencies\n      - restore_cache:\n          keys:\n            - v1-dependencies-{{ checksum \"project.clj\" }}\n            # fallback to using the latest cache if no exact match is found\n            - v1-dependencies-\n      - run:\n         name: Install babashka\n         command: |\n           curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install.sh\n           sudo bash install.sh\n           rm install.sh\n\n      - run:\n          name: install karma\n          command: |\n            sudo npm install -g karma-cli\n            sudo npm install\n\n      - run: lein deps\n\n      - save_cache:\n          paths:\n            - ~/.m2\n          key: v1-dependencies-{{ checksum \"project.clj\" }}\n\n      # run tests!\n      - run: bb test:clj\n      - run: bb test:cljs\n\n\n  deploy:\n    docker:\n      - image: cimg/clojure:1.11.1-browsers\n\n    working_directory: ~/repo\n\n    environment:\n      LEIN_ROOT: \"true\"\n      # Customize the JVM maximum heap limit\n      JVM_OPTS: -Xmx3200m\n\n    steps:\n      - checkout\n\n      # Download and cache dependencies\n      - restore_cache:\n          keys:\n            - v1-dependencies-{{ checksum \"project.clj\" }}\n            # fallback to using the latest cache if no exact match is found\n            - v1-dependencies-\n\n      # Download and cache dependencies\n      - restore_cache:\n          keys:\n            - v1-dependencies-{{ checksum \"project.clj\" }}\n            # fallback to using the latest cache if no exact match is found\n            - v1-dependencies-\n\n      - run:\n          name: Install babashka\n          command: |\n            curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install.sh\n            sudo bash install.sh\n            rm install.sh\n\n      - run:\n          name: Install deployment-script\n          command: |\n            curl -s https://raw.githubusercontent.com/clj-commons/infra/main/deployment/circle-maybe-deploy.bb -o circle-maybe-deploy.bb\n            chmod a+x circle-maybe-deploy.bb\n      - run: lein deps\n\n      - run:\n          name: Setup GPG signing key\n          command: |\n            GNUPGHOME=\"$HOME/.gnupg\"\n            export GNUPGHOME\n            mkdir -p \"$GNUPGHOME\"\n            chmod 0700 \"$GNUPGHOME\"\n              echo \"$GPG_KEY\" \\\n               | base64 --decode --ignore-garbage \\\n               | gpg --batch --allow-secret-key-import --import\n               gpg --keyid-format LONG --list-secret-keys\n      - save_cache:\n          paths:\n            - ~/.m2\n          key: v1-dependencies-{{ checksum \"project.clj\" }}\n      - run:\n         name: Deploy\n         command: |\n           GPG_TTY=$(tty)\n           export GPG_TTY\n           echo $GPG_TTY\n           ./circle-maybe-deploy.bb lein deploy clojars\n"
  },
  {
    "path": ".clj-kondo/babashka/fs/config.edn",
    "content": "{:lint-as {babashka.fs/with-temp-dir clojure.core/let}}\n"
  },
  {
    "path": ".clj-kondo/rewrite-clj/rewrite-clj/config.edn",
    "content": "{:lint-as\n {rewrite-clj.zip/subedit-> clojure.core/->\n  rewrite-clj.zip/subedit->> clojure.core/->>\n  rewrite-clj.zip/edit-> clojure.core/->\n  rewrite-clj.zip/edit->> clojure.core/->>}}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @port19x\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n\non: [push, pull_request]\n\njobs:\n\n  clojure:\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n\n      # It is important to install java before installing clojure tools which needs java\n      # exclusions: babashka, clj-kondo and cljstyle\n      - name: Prepare java\n        uses: actions/setup-java@v3\n        with:\n          distribution: 'zulu'\n          java-version: '8'\n\n      - name: Install clojure tools\n        uses: DeLaGuardo/setup-clojure@10.0\n        with:\n          bb: latest\n          lein: latest\n      # Optional step:\n      - name: Cache clojure dependencies\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.m2/repository\n            ~/.gitlibs\n            ~/.deps.clj\n          # List all files containing dependencies:\n          key: cljdeps-${{ hashFiles('deps.edn') }}\n          # key: cljdeps-${{ hashFiles('deps.edn', 'bb.edn') }}\n          # key: cljdeps-${{ hashFiles('project.clj') }}\n          # key: cljdeps-${{ hashFiles('build.boot') }}\n          restore-keys: cljdeps-\n\n      - name: Execute clj tests\n        run: bb test:clj\n\n      - name: Execute cljs tests\n        run: |\n          bb test:cljs-npm-install\n          bb test:cljs -c test/cljs/hickory/advanced.edn\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/lib\n/classes\n/checkouts\n/codox-out\npom.xml\n*.jar\n*.class\n.lein-deps-sum\n.lein-failures\n.lein-plugins\n.lein-repl-history\nout/\n.cpcache\n.cache\n.clj-kondo/.cache\n.lsp/.cache\n.portal/vs-code.edn\ncljs-test-runner-out\nnode_modules\n\npackage-lock.json\n"
  },
  {
    "path": "API.md",
    "content": "# Table of contents\n-  [`hickory.core`](#hickory.core) \n    -  [`Attribute`](#hickory.core/attribute)\n    -  [`Comment`](#hickory.core/comment)\n    -  [`Document`](#hickory.core/document)\n    -  [`DocumentType`](#hickory.core/documenttype)\n    -  [`Element`](#hickory.core/element)\n    -  [`HiccupRepresentable`](#hickory.core/hiccuprepresentable) - Objects that can be represented as Hiccup nodes implement this protocol in order to make the conversion.\n    -  [`HickoryRepresentable`](#hickory.core/hickoryrepresentable) - Objects that can be represented as HTML DOM node maps, similar to clojure.xml, implement this protocol to make the conversion.\n    -  [`Text`](#hickory.core/text)\n    -  [`as-hiccup`](#hickory.core/as-hiccup) - Converts the node given into a hiccup-format data structure.\n    -  [`as-hickory`](#hickory.core/as-hickory) - Converts the node given into a hickory-format data structure.\n    -  [`extract-doctype`](#hickory.core/extract-doctype)\n    -  [`format-doctype`](#hickory.core/format-doctype)\n    -  [`node-type`](#hickory.core/node-type)\n    -  [`parse`](#hickory.core/parse) - Parse an entire HTML document into a DOM structure that can be used as input to as-hiccup or as-hickory.\n    -  [`parse-dom-with-domparser`](#hickory.core/parse-dom-with-domparser)\n    -  [`parse-dom-with-write`](#hickory.core/parse-dom-with-write) - Parse an HTML document (or fragment) as a DOM using document.implementation.createHTMLDocument and document.write.\n    -  [`parse-fragment`](#hickory.core/parse-fragment) - Parse an HTML fragment (some group of tags that might be at home somewhere in the tag hierarchy under <body>) into a list of DOM elements that can each be passed as input to as-hiccup or as-hickory.\n    -  [`remove-el`](#hickory.core/remove-el)\n\n-----\n# <a name=\"hickory.core\">hickory.core</a>\n\n\n\n\n\n\n## <a name=\"hickory.core/attribute\">`Attribute`</a>\n\n\n\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L52-L52\">Source</a></sub></p>\n\n## <a name=\"hickory.core/comment\">`Comment`</a>\n\n\n\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L53-L53\">Source</a></sub></p>\n\n## <a name=\"hickory.core/document\">`Document`</a>\n\n\n\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L54-L54\">Source</a></sub></p>\n\n## <a name=\"hickory.core/documenttype\">`DocumentType`</a>\n\n\n\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L55-L55\">Source</a></sub></p>\n\n## <a name=\"hickory.core/element\">`Element`</a>\n\n\n\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L56-L56\">Source</a></sub></p>\n\n## <a name=\"hickory.core/hiccuprepresentable\">`HiccupRepresentable`</a>\n\n\n\n\nObjects that can be represented as Hiccup nodes implement this protocol in\n   order to make the conversion.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L12-L18\">Source</a></sub></p>\n\n## <a name=\"hickory.core/hickoryrepresentable\">`HickoryRepresentable`</a>\n\n\n\n\nObjects that can be represented as HTML DOM node maps, similar to\n   clojure.xml, implement this protocol to make the conversion.\n\n   Each DOM node will be a map or string (for Text/CDATASections). Nodes that\n   are maps have the appropriate subset of the keys\n\n     :type     - [:comment, :document, :document-type, :element]\n     :tag      - node's tag, check :type to see if applicable\n     :attrs    - node's attributes as a map, check :type to see if applicable\n     :content  - node's child nodes, in a vector, check :type to see if\n                 applicable\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L20-L35\">Source</a></sub></p>\n\n## <a name=\"hickory.core/text\">`Text`</a>\n\n\n\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L57-L57\">Source</a></sub></p>\n\n## <a name=\"hickory.core/as-hiccup\">`as-hiccup`</a>\n``` clojure\n\n(as-hiccup this)\n```\nFunction.\n\nConverts the node given into a hiccup-format data structure. The\n     node must have an implementation of the HiccupRepresentable\n     protocol; nodes created by parse or parse-fragment already do.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L15-L18\">Source</a></sub></p>\n\n## <a name=\"hickory.core/as-hickory\">`as-hickory`</a>\n``` clojure\n\n(as-hickory this)\n```\nFunction.\n\nConverts the node given into a hickory-format data structure. The\n     node must have an implementation of the HickoryRepresentable protocol;\n     nodes created by parse or parse-fragment already do.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L32-L35\">Source</a></sub></p>\n\n## <a name=\"hickory.core/extract-doctype\">`extract-doctype`</a>\n``` clojure\n\n(extract-doctype s)\n```\nFunction.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L121-L126\">Source</a></sub></p>\n\n## <a name=\"hickory.core/format-doctype\">`format-doctype`</a>\n``` clojure\n\n(format-doctype dt)\n```\nFunction.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L62-L69\">Source</a></sub></p>\n\n## <a name=\"hickory.core/node-type\">`node-type`</a>\n``` clojure\n\n(node-type type)\n```\nFunction.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L37-L50\">Source</a></sub></p>\n\n## <a name=\"hickory.core/parse\">`parse`</a>\n``` clojure\n\n(parse s)\n```\nFunction.\n\nParse an entire HTML document into a DOM structure that can be\n   used as input to as-hiccup or as-hickory.\n\n```klipse\n  (-> (parse \"<a style=\\\"visibility:hidden\\\">foo</a><div style=\\\"color:green\\\"><p>Hello</p></div>\")\n    as-hiccup)\n```\n\n```klipse\n  (-> (parse \"<a style=\\\"visibility:hidden\\\">foo</a><div style=\\\"color:green\\\"><p>Hello</p></div>\")\n    as-hickory)\n```\n\n\n  \n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L151-L168\">Source</a></sub></p>\n\n## <a name=\"hickory.core/parse-dom-with-domparser\">`parse-dom-with-domparser`</a>\n``` clojure\n\n(parse-dom-with-domparser s)\n```\nFunction.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L132-L135\">Source</a></sub></p>\n\n## <a name=\"hickory.core/parse-dom-with-write\">`parse-dom-with-write`</a>\n``` clojure\n\n(parse-dom-with-write s)\n```\nFunction.\n\nParse an HTML document (or fragment) as a DOM using document.implementation.createHTMLDocument and document.write.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L137-L149\">Source</a></sub></p>\n\n## <a name=\"hickory.core/parse-fragment\">`parse-fragment`</a>\n``` clojure\n\n(parse-fragment s)\n```\nFunction.\n\nParse an HTML fragment (some group of tags that might be at home somewhere\n   in the tag hierarchy under <body>) into a list of DOM elements that can\n   each be passed as input to as-hiccup or as-hickory.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L170-L175\">Source</a></sub></p>\n\n## <a name=\"hickory.core/remove-el\">`remove-el`</a>\n``` clojure\n\n(remove-el el)\n```\nFunction.\n<p><sub><a href=\"https://github.com/clj-commons/hickory/blob/master/src/cljs/hickory/core.cljs#L128-L130\">Source</a></sub></p>\n"
  },
  {
    "path": "LICENSE",
    "content": "Source code distributed under the Eclipse Public License - v 1.0:\n\nTHE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE\nPUBLIC LICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION OR DISTRIBUTION OF\nTHE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.\n\n1. DEFINITIONS\n\n\"Contribution\" means:\n\na) in the case of the initial Contributor, the initial code and\ndocumentation distributed under this Agreement, and\n\nb) in the case of each subsequent Contributor:\n\ni) changes to the Program, and\n\nii) additions to the Program;\n\nwhere such changes and/or additions to the Program originate from and\nare distributed by that particular Contributor. A Contribution\n'originates' from a Contributor if it was added to the Program by such\nContributor itself or anyone acting on such Contributor's\nbehalf. Contributions do not include additions to the Program which:\n(i) are separate modules of software distributed in conjunction with\nthe Program under their own license agreement, and (ii) are not\nderivative works of the Program.\n\n\"Contributor\" means any person or entity that distributes the Program.\n\n\"Licensed Patents\" mean patent claims licensable by a Contributor\nwhich are necessarily infringed by the use or sale of its Contribution\nalone or when combined with the Program.\n\n\"Program\" means the Contributions distributed in accordance with this\nAgreement.\n\n\"Recipient\" means anyone who receives the Program under this\nAgreement, including all Contributors.\n\n2. GRANT OF RIGHTS\n\na) Subject to the terms of this Agreement, each Contributor hereby\ngrants Recipient a non-exclusive, worldwide, royalty-free copyright\nlicense to reproduce, prepare derivative works of, publicly display,\npublicly perform, distribute and sublicense the Contribution of such\nContributor, if any, and such derivative works, in source code and\nobject code form.\n\nb) Subject to the terms of this Agreement, each Contributor hereby\ngrants Recipient a non-exclusive, worldwide, royalty-free patent\nlicense under Licensed Patents to make, use, sell, offer to sell,\nimport and otherwise transfer the Contribution of such Contributor, if\nany, in source code and object code form.  This patent license shall\napply to the combination of the Contribution and the Program if, at\nthe time the Contribution is added by the Contributor, such addition\nof the Contribution causes such combination to be covered by the\nLicensed Patents. The patent license shall not apply to any other\ncombinations which include the Contribution. No hardware per se is\nlicensed hereunder.\n\nc) Recipient understands that although each Contributor grants the\nlicenses to its Contributions set forth herein, no assurances are\nprovided by any Contributor that the Program does not infringe the\npatent or other intellectual property rights of any other entity. Each\nContributor disclaims any liability to Recipient for claims brought by\nany other entity based on infringement of intellectual property rights\nor otherwise. As a condition to exercising the rights and licenses\ngranted hereunder, each Recipient hereby assumes sole responsibility\nto secure any other intellectual property rights needed, if any. For\nexample, if a third party patent license is required to allow\nRecipient to distribute the Program, it is Recipient's responsibility\nto acquire that license before distributing the Program.\n\nd) Each Contributor represents that to its knowledge it has sufficient\ncopyright rights in its Contribution, if any, to grant the copyright\nlicense set forth in this Agreement.\n\n3. REQUIREMENTS\n\nA Contributor may choose to distribute the Program in object code form\nunder its own license agreement, provided that:\n\na) it complies with the terms and conditions of this Agreement; and\n\nb) its license agreement:\n\ni) effectively disclaims on behalf of all Contributors all warranties\nand conditions, express and implied, including warranties or\nconditions of title and non-infringement, and implied warranties or\nconditions of merchantability and fitness for a particular purpose;\n\nii) effectively excludes on behalf of all Contributors all liability\nfor damages, including direct, indirect, special, incidental and\nconsequential damages, such as lost profits;\n\niii) states that any provisions which differ from this Agreement are\noffered by that Contributor alone and not by any other party; and\n\niv) states that source code for the Program is available from such\nContributor, and informs licensees how to obtain it in a reasonable\nmanner on or through a medium customarily used for software exchange.\n\nWhen the Program is made available in source code form:\n\na) it must be made available under this Agreement; and\n\nb) a copy of this Agreement must be included with each copy of the Program.\n\nContributors may not remove or alter any copyright notices contained\nwithin the Program.\n\nEach Contributor must identify itself as the originator of its\nContribution, if any, in a manner that reasonably allows subsequent\nRecipients to identify the originator of the Contribution.\n\n4. COMMERCIAL DISTRIBUTION\n\nCommercial distributors of software may accept certain\nresponsibilities with respect to end users, business partners and the\nlike. While this license is intended to facilitate the commercial use\nof the Program, the Contributor who includes the Program in a\ncommercial product offering should do so in a manner which does not\ncreate potential liability for other Contributors. Therefore, if a\nContributor includes the Program in a commercial product offering,\nsuch Contributor (\"Commercial Contributor\") hereby agrees to defend\nand indemnify every other Contributor (\"Indemnified Contributor\")\nagainst any losses, damages and costs (collectively \"Losses\") arising\nfrom claims, lawsuits and other legal actions brought by a third party\nagainst the Indemnified Contributor to the extent caused by the acts\nor omissions of such Commercial Contributor in connection with its\ndistribution of the Program in a commercial product offering.  The\nobligations in this section do not apply to any claims or Losses\nrelating to any actual or alleged intellectual property\ninfringement. In order to qualify, an Indemnified Contributor must: a)\npromptly notify the Commercial Contributor in writing of such claim,\nand b) allow the Commercial Contributor tocontrol, and cooperate with\nthe Commercial Contributor in, the defense and any related settlement\nnegotiations. The Indemnified Contributor may participate in any such\nclaim at its own expense.\n\nFor example, a Contributor might include the Program in a commercial\nproduct offering, Product X. That Contributor is then a Commercial\nContributor. If that Commercial Contributor then makes performance\nclaims, or offers warranties related to Product X, those performance\nclaims and warranties are such Commercial Contributor's responsibility\nalone. Under this section, the Commercial Contributor would have to\ndefend claims against the other Contributors related to those\nperformance claims and warranties, and if a court requires any other\nContributor to pay any damages as a result, the Commercial Contributor\nmust pay those damages.\n\n5. NO WARRANTY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS\nPROVIDED ON AN \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY\nWARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY\nOR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely\nresponsible for determining the appropriateness of using and\ndistributing the Program and assumes all risks associated with its\nexercise of rights under this Agreement , including but not limited to\nthe risks and costs of program errors, compliance with applicable\nlaws, damage to or loss of data, programs or equipment, and\nunavailability or interruption of operations.\n\n6. DISCLAIMER OF LIABILITY\n\nEXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR\nANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING\nWITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR\nDISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED\nHEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n\n7. GENERAL\n\nIf any provision of this Agreement is invalid or unenforceable under\napplicable law, it shall not affect the validity or enforceability of\nthe remainder of the terms of this Agreement, and without further\naction by the parties hereto, such provision shall be reformed to the\nminimum extent necessary to make such provision valid and enforceable.\n\nIf Recipient institutes patent litigation against any entity\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nthe Program itself (excluding combinations of the Program with other\nsoftware or hardware) infringes such Recipient's patent(s), then such\nRecipient's rights granted under Section 2(b) shall terminate as of\nthe date such litigation is filed.\n\nAll Recipient's rights under this Agreement shall terminate if it\nfails to comply with any of the material terms or conditions of this\nAgreement and does not cure such failure in a reasonable period of\ntime after becoming aware of such noncompliance. If all Recipient's\nrights under this Agreement terminate, Recipient agrees to cease use\nand distribution of the Program as soon as reasonably\npracticable. However, Recipient's obligations under this Agreement and\nany licenses granted by Recipient relating to the Program shall\ncontinue and survive.\n\nEveryone is permitted to copy and distribute copies of this Agreement,\nbut in order to avoid inconsistency the Agreement is copyrighted and\nmay only be modified in the following manner. The Agreement Steward\nreserves the right to publish new versions (including revisions) of\nthis Agreement from time to time. No one other than the Agreement\nSteward has the right to modify this Agreement. The Eclipse Foundation\nis the initial Agreement Steward. The Eclipse Foundation may assign\nthe responsibility to serve as the Agreement Steward to a suitable\nseparate entity. Each new version of the Agreement will be given a\ndistinguishing version number. The Program (including Contributions)\nmay always be distributed subject to the version of the Agreement\nunder which it was received. In addition, after a new version of the\nAgreement is published, Contributor may elect to distribute the\nProgram (including its Contributions) under the new version. Except as\nexpressly stated in Sections 2(a) and 2(b) above, Recipient receives\nno rights or licenses to the intellectual property of any Contributor\nunder this Agreement, whether expressly, by implication, estoppel or\notherwise. All rights in the Program not expressly granted under this\nAgreement are reserved.\n\nThis Agreement is governed by the laws of the State of Washington and\nthe intellectual property laws of the United States of America. No\nparty to this Agreement will bring a legal action under this Agreement\nmore than one year after the cause of action arose. Each party waives\nits rights to a jury trial in any resulting litigation.\n"
  },
  {
    "path": "ORIGINATOR",
    "content": "@davidsantiago\n"
  },
  {
    "path": "README.md",
    "content": "[![Clojars Project](https://img.shields.io/clojars/v/org.clj-commons/hickory.svg)](https://clojars.org/org.clj-commons/hickory)\n[![cljdoc badge](https://cljdoc.org/badge/org.clj-commons/hickory)](https://cljdoc.org/d/org.clj-commons/hickory)\n[![CircleCI](https://circleci.com/gh/clj-commons/hickory.svg?style=svg)](https://circleci.com/gh/clj-commons/hickory)\n\n# Hickory\n\nHickory parses HTML into Clojure data structures, so you can analyze,\ntransform, and output back to HTML. HTML can be parsed into\n[hiccup](http://github.com/weavejester/hiccup) vectors, or into a\nmap-based DOM-like format very similar to that used by clojure.xml. It\ncan be used from both Clojure and Clojurescript.\n\n\n## Usage\n\n### Parsing\n\nTo start, you will want to process your HTML into a parsed\nrepresentation. Once the HTML is in this form, it can be converted to\neither Hiccup or Hickory format for further processing. There are two\nparsing functions, `parse` and `parse-fragment`. Both take a string\ncontaining HTML and return the parser objects representing the\ndocument. (It happens that these parser objects are Jsoup Documents\nand Nodes, but I do not consider this to be an aspect worth preserving\nif a change in parser should become necessary).\n\nThe first function, `parse` expects an entire HTML document, and\nparses it using an HTML5 parser ([Jsoup](http://jsoup.org) on Clojure and\nthe browser's DOM parser in Clojurescript), which will\nfix up the HTML as much as it can into a well-formed document. The\nsecond function, `parse-fragment`, expects some smaller fragment of\nHTML that does not make up a full document, and thus returns a list of\nparsed fragments, each of which must be processed individually into\nHiccup or Hickory format. For example, if `parse-fragment` is given\n\"`<p><br>`\" as input, it has no common parent for them, so it must\nsimply give you the list of nodes that it parsed.\n\nThese parsed objects can be turned into either Hiccup vector trees or\nHickory DOM maps using the functions `as-hiccup` or `as-hickory`.\n\nHere's a usage example.\n\n```clojure\nuser=> (use 'hickory.core)\nnil\nuser=> (def parsed-doc (parse \"<a href=\\\"foo\\\">foo</a>\"))\n#'user/parsed-doc\nuser=> (as-hiccup parsed-doc)\n([:html {} [:head {}] [:body {} [:a {:href \"foo\"} \"foo\"]]])\nuser=> (as-hickory parsed-doc)\n{:type :document, :content [{:type :element, :attrs nil, :tag :html, :content [{:type :element, :attrs nil, :tag :head, :content nil} {:type :element, :attrs nil, :tag :body, :content [{:type :element, :attrs {:href \"foo\"}, :tag :a, :content [\"foo\"]}]}]}]}\nuser=> (def parsed-frag (parse-fragment \"<a href=\\\"foo\\\">foo</a> <a href=\\\"bar\\\">bar</a>\"))\n#'user/parsed-frag\nuser=> (as-hiccup parsed-frag)\nIllegalArgumentException No implementation of method: :as-hiccup of protocol: #'hickory.core/HiccupRepresentable found for class: clojure.lang.PersistentVector  clojure.core/-cache-protocol-fn (core_deftype.clj:495)\n\nuser=> (map as-hiccup parsed-frag)\n([:a {:href \"foo\"} \"foo\"] \" \" [:a {:href \"bar\"} \"bar\"])\nuser=> (map as-hickory parsed-frag)\n({:type :element, :attrs {:href \"foo\"}, :tag :a, :content [\"foo\"]} \" \" {:type :element, :attrs {:href \"bar\"}, :tag :a, :content [\"bar\"]})\n```\n\nIn the example above, you can see an HTML document that is parsed once\nand then converted to both Hiccup and Hickory formats. Similarly, a\nfragment is parsed, but it cannot be directly used with `as-hiccup`\n(or `as-hickory`), it must have those functions called on each element\nin the list instead.\n\nThe namespace `hickory.zip` provides\n[zippers](https://clojure.github.io/clojure/clojure.zip-api.html) for\nboth Hiccup and Hickory formatted data, with the functions\n`hiccup-zip` and `hickory-zip`. Using zippers, you can easily traverse\nthe trees in any order you desire, make edits, and get the resulting\ntree back. Here is an example of that.\n\n```clojure\nuser=> (use 'hickory.zip)\nnil\nuser=> (require '[clojure.zip :as zip])\nnil\nuser=> (require '[hickory.render :refer [hickory-to-html]])\nnil\nuser=> (-> (hiccup-zip (as-hiccup (parse \"<a href=foo>bar<br></a>\"))) zip/node)\n([:html {} [:head {}] [:body {} [:a {:href \"foo\"} \"bar\" [:br {}]]]])\nuser=> (-> (hiccup-zip (as-hiccup (parse \"<a href=foo>bar<br></a>\"))) zip/next zip/node)\n[:html {} [:head {}] [:body {} [:a {:href \"foo\"} \"bar\" [:br {}]]]]\nuser=> (-> (hiccup-zip (as-hiccup (parse \"<a href=foo>bar<br></a>\"))) zip/next zip/next zip/node)\n[:head {}]\nuser=> (-> (hiccup-zip (as-hiccup (parse \"<a href=foo>bar<br></a>\")))\n           zip/next zip/next\n           (zip/replace [:head {:id \"a\"}])\n           zip/node)\n[:head {:id \"a\"}]\nuser=> (-> (hiccup-zip (as-hiccup (parse \"<a href=foo>bar<br></a>\")))\n           zip/next zip/next\n           (zip/replace [:head {:id \"a\"}])\n           zip/root)\n([:html {} [:head {:id \"a\"}] [:body {} [:a {:href \"foo\"} \"bar\" [:br {}]]]])\nuser=> (-> (hickory-zip (as-hickory (parse \"<a href=foo>bar<br></a>\")))\n           zip/next zip/next\n           (zip/replace {:type :element :tag :head :attrs {:id \"a\"} :content nil})\n           zip/root)\n{:type :document, :content [{:type :element, :attrs nil, :tag :html, :content [{:content nil, :type :element, :attrs {:id \"a\"}, :tag :head} {:type :element, :attrs nil, :tag :body, :content [{:type :element, :attrs {:href \"foo\"}, :tag :a, :content [\"bar\" {:type :element, :attrs nil, :tag :br, :content nil}]}]}]}]}\nuser=> (hickory-to-html *1)\n\"<html><head id=\\\"a\\\"></head><body><a href=\\\"foo\\\">bar<br></a></body></html>\"\n```\n\nIn this example, we can see a basic document being parsed into Hiccup\nform. Then, using zippers, the HEAD element is navigated to, and then\nreplaced with one that has an id of \"a\". The final tree, including the\nmodification, is also shown using `zip/root`. Then the same\nmodification is made using Hickory forms and zippers. Finally, the\nmodified Hickory version is printed back to HTML using the\n`hickory-to-html` function.\n\n### Selectors\n\nHickory also comes with a set of CSS-style selectors that operate on\nhickory-format data in the `hickory.select` namespace. These selectors\ndo not exactly mirror the selectors in CSS, and are often more\npowerful. There is no version of these selectors for hiccup-format\ndata, at this point.\n\nA selector is simply a function that takes a zipper loc from a hickory\nhtml tree data structure as its only argument. The selector will\nreturn its argument if the selector applies to it, and nil\notherwise. Writing useful selectors can often be involved, so most of\nthe `hickory.select` package is actually made up of selector\ncombinators; functions that return useful selector functions by\nspecializing them to the data given as arguments, or by combining\ntogether multiple selectors. For example, if we wanted to figure out\nthe dates of the next Formula 1 race weekend, we could do something\nlike this:\n\n```clojure\nuser=> (use 'hickory.core)\nnil\nuser=> (require '[hickory.select :as s])\nnil\nuser=> (require '[clj-http.client :as client])\nnil\nuser=> (require '[clojure.string :as string])\nnil\nuser=> (def site-htree (-> (client/get \"http://formula1.com/default.html\") :body parse as-hickory))\n#'user/site-htree\nuser=> (-> (s/select (s/child (s/class \"subCalender\") ; sic\n                              (s/tag :div)\n                              (s/id :raceDates)\n                              s/first-child\n                              (s/tag :b))\n                     site-htree)\n           first :content first string/trim)\n\"10, 11, 12 May 2013\"\n```\n\nIn this example, we get the contents of the homepage and use `select`\nto give us any nodes that satisfy the criteria laid out by the\nselectors. The selector in this example is overly precise in order to\nillustrate more selectors than we need; we could have gotten by just\nselecting the contents of the P and then B tags inside the element\nwith id \"raceDates\".\n\nUsing the selectors allows you to search large HTML documents for nodes of interest with a relatively small amount of code. There are many selectors available in the [`hickory.select`](https://cljdoc.org/d/org.clj-commons/hickory/CURRENT/api/hickory.select) namespace, including:\n\n- `node-type`: Give this function a keyword or string that names the contents of the `:type` field in a hickory node, and it gives you a selector that will select nodes of that type. Example: `(node-type :comment)`\n- `tag`: Give this function a keyword or string that names the contents of the `:tag` field in a hickory node, and it gives you a selector that will select nodes with that tag. Example: `(tag :div)`\n- `attr`: Give this function a keyword or string that names an attribute in the `:attrs` map of a hickory node, and it gives you a selector that will select nodes whose `:attrs` map contains that key. Give a single-argument function as an additional argument, and the resulting selector function will additionally require the value of that key to be such that the function given as the last argument returns true. Example: `(attr :id #(.startsWith % \"foo\"))`\n- `id`: Give this function a keyword or string that names the `:id` attribute in the `:attrs` map and it will return a selector function that selects nodes that have that id (this comparison is case-insensitive). Example: `(id :raceDates)`\n- `class`: Give this function a keyword or string that names a class that the node should have in the `:class` attribute in the `:attrs` map, and it will return a function that selects nodes that have the given class somewhere in their class string. Example: `(class :foo)`\n- `any`: This selector takes no arguments, do not invoke it; returns any node that is an element, similarly to CSS's '*' selector.\n- `element`: This selector is equivalent to the `any` selector; this alternate name can make it clearer when the intention is to exclude non-element nodes from consideration.\n- `root`: This selector takes no arguments and should not be invoked; simply returns the root node (the HTML element).\n- `n-moves-until`: This selector returns a selector function that selects its argument if that argument is some distance from a boundary. The first two arguments, `n` and `c` define the counting: it only selects nodes whose distance can be written in the form `nk+c` for some natural number `k`. The distance and boundary are defined by the number of times the zipper-movement function in the third argument is applied before the boundary function in the last argument is true. See doc string for details.\n- `nth-of-type`: This selector returns a selector function that selects its argument if that argument is the `(nk+c)`'th child of the given tag type of some parent node for some natural `k`. Optionally, instead of the `n` and `c` arguments, the keywords `:odd` and `:even` can be given.\n- `nth-last-of-type`: Just like `nth-of-type` but counts backwards from the last sibling.\n- `nth-child`: This selector returns a selector function that selects its argument if that argument is the `(nk+c)`'th child of its parent node for some natural `k`. Instead of the `n` and `c` arguments, the keywords `:odd` and `:even` can be given.\n- `nth-last-child`: Just like `nth-last-child` but counts backwards from the last sibling.\n- `first-child`: Takes no arguments, do not invoke it; equivalent to `(nth-child 1)`.\n- `last-child`: Takes no arguments, do not invoke it; equivalent to `(nth-last-child 1)`.\n\nThere are also selector combinators, which take as argument some number of other selectors, and return a new selector that combines them into one larger selector. An example of this is the `child` selector in the example above. Here's a list of some selector combinators in the package (see the [API Documentation](https://cljdoc.org/d/org.clj-commons/hickory) for the full list):\n\n- `and`: Takes any number of selectors, and returns a selector that only selects nodes for which all of the argument selectors are true.\n- `or`: Takes any number of selectors, and retrurns a selector that only selects nodes for which at least one of the argument selectors are true.\n- `not`: Takes a single selector as argument and returns a selector that only selects nodes that its argument selector does not.\n- `el-not`: Takes a single selector as argument and returns a selector that only selects element nodes that its argument selector does not.\n- `child`: Takes any number of selectors as arguments and returns a selector that returns true when the zipper location given as the argument is at the end of a chain of direct child relationships specified by the selectors given as arguments.\n- `descendant`: Takes any number of selectors as arguments and returns a selector that returns true when the zipper location given as the argument is at the end of a chain of descendant relationships specified by the selectors given as arguments.\n\nWe can illustrate the selector combinators by continuing the Formula 1 example above. We suspect, to our dismay, that Sebastian Vettel is leading the championship for the fourth year in a row.\n\n```clojure\nuser=> (-> (s/select (s/descendant (s/class \"subModule\")\n                                   (s/class \"standings\")\n                                   (s/and (s/tag :tr)\n                                          s/first-child)\n                                   (s/and (s/tag :td)\n                                          (s/nth-child 2))\n                                   (s/tag :a))\n                     site-htree)\n           first :content first string/trim)\n\"Sebastian Vettel\"\n```\n\nOur fears are confirmed, Sebastian Vettel is well on his way to a fourth consecutive championship. If you were to inspect the page by hand (as of around May 2013, at least), you would see that unlike the `child` selector we used in the example above, the `descendant` selector allows the argument selectors to skip stages in the tree; we've left out some elements in this descendant relationship. The first table row in the driver standings table is selected with the `and`, `tag` and `first-child` selectors, and then the second `td` element is chosen, which is the element that has the driver's name (the first table element has the driver's standing) inside an `A` element. All of this is dependent on the exact layout of the HTML in the site we are examining, of course, but it should give an idea of how you can combine selectors to reach into a specific node of an HTML document very easily.\n\nFinally, it's worth noting that the `select` function itself returns the hickory zipper nodes it finds. This is most useful for analyzing the contents of nodes. However, sometimes you may wish to examine the area around a node once you've found it. For this, you can use the `select-locs` function, which returns a sequence of hickory zipper locs, instead of the nodes themselves. This will allow you to navigate around the document tree using the zipper functions in `clojure.zip`. If you wish to go further and actually modify the document tree using zipper functions, you should not use `select-locs`. The problem is that it returns a bunch of zipper locs, but once you modify one, the others are out of date and do not see the changes (just as with any other persistent data structure in Clojure). Thus, their presence was useless and possibly confusing. Instead, you should use the `select-next-loc` function to walk through the document tree manually, moving through the locs that satisfy the selector function one by one, which will allow you to make modifications as you go. As with modifying any data structure as you traverse it, you must still be careful that your code does not add the thing it is selecting for, or it could get caught in an infinite loop. Finally, for more specialized selection needs, it should be possible to write custom selection functions that use the selectors and zipper functions without too much work. The functions discussed in this paragraph are very short and simple, you can use them as a guide.\n\nThe doc strings for the functions in the [`hickory.select`](https://cljdoc.org/d/org.clj-commons/hickory/CURRENT/api/hickory.select) namespace provide more details on most of these functions.\n\nFor more details, see the [API Documentation](https://cljdoc.org/d/org.clj-commons/hickory/).\n\n## Hickory format\n\nWhy two formats? It's very easy to see in the example above, Hiccup is\nvery convenient to use for writing HTML. It has a compact syntax, with\nCSS-like shortcuts for specifying classes and ids. It also allows\nparts of the vector to be skipped if they are not important.\n\nIt's a little bit harder to process data in Hiccup format. First of\nall, each form has to be checked for the presence of the attribute\nmap, and the traversal adjusted accordingly. Raw Hiccup vectors might\nalso have information about class and id in one of two different\nplaces. Finally, not every piece of an HTML document can be expressed\nin Hiccup without resorting to writing HTML in strings. For example,\nif you want to put a doctype or comment on your document, it has to be\ndone as a string in your Hiccup form containing \"`<!DOCTYPE html>`\" or\n\"`<!--stuff-->`\".\n\nThe Hickory format is another data format intended to allow a\nroundtrip from HTML as text, into a data structure that is easy to\nprocess and modify, and back into equivalent (but not identical, in\ngeneral) HTML. Because it can express all parts of an HTML document in\na parsed form, it is easier to search and modify the structure of the\ndocument.\n\nA Hickory node is either a map or a string. If it is a map, it will\nhave some subset of the following four keys, depending on the `:type`:\n\n- `:type`    - This will be one of `:comment`, `:document`, `:document-type`, `:element`\n- `:tag`     - A node's tag (for example, `:img`). This will only be present for nodes of type `:element`.\n- `:attrs`   - A node's attributes, as a map of keywords to values (for example, {:href \"/a\"}). This will only be present for nodes of type `:element`.\n- `:content` - A node's child nodes, in a vector. Only `:comment`, `:document`, and `:element` nodes have children.\n\nText and CDATA nodes are represented as strings.\n\nThis is almost the exact same structure used by\n[clojure.xml](https://clojure.github.io/clojure/clojure.xml-api.html),\nthe only difference being the addition of the `:type` field. Having\nthis field allows us to process nodes that clojure.xml leaves out of\nthe parsed data, like doctype and comments.\n\n## Obtaining\n\nTo get hickory, add\n\n```clojure\n[org.clj-commons/hickory \"0.7.3\"]\n```\n\nto your project.clj, or an equivalent entry for your Maven-compatible build tool.\n\n## ClojureScript support\n\nHickory works for all web browsers IE9+ (you can find a workaround for IE9 [here](http://stackoverflow.com/questions/9250545/javascript-domparser-access-innerhtml-and-other-properties)).\n\n## Nodejs support\n\nTo parse markup on Nodejs, Hickory requires a Node DOM implementation.\nSeveral are available from [npm](https://www.npmjs.com).\nInstall the npm package or use [lein-npm](https://github.com/RyanMcG/lein-npm).\nHere are some alternatives:\n\n- [jsdom](https://www.npmjs.com/package/jsdom) - **Caution:** this will not work if you're using figwheel\n\n    ```clojure\n\t(set! js/document (.jsdom (cljs.nodejs/require \"jsdom\")))\n\t```\n\n- [xmldom](https://www.npmjs.com/package/xmldom)\n\n    ```clojure\n\t(set! js/DOMParser (.-DOMParser (cljs.nodejs/require \"xmldom\")))\n\t```\n\n## Changes\n\n- Version 0.7.1. Thanks to [Matt Grimm](https://github.com/tkocmathla) for adding the up-pred zipper function.\n\n- Version 0.7.0. Thanks to [Ricardo J. Méndez](https://github.com/ricardojmendez) for the following updates.\n    * Removed dependency on cljx, since it was deprecated in June 2015.\n    * Converted all files and conditionals to cljc.\n    * Moved tests to cljs.test with doo, since cemerick.test was deprecated over a year ago.\n    * Updated Clojure and ClojureScript dependencies to avoid conflicts.\n    * Updated JSoup to 1.9.2, which should bring improved parsing performance.\n\n- Released version 0.6.0.\n    * Updated JSoup to version 1.8.3. This version of JSoup contains bug fixes, but slightly changes the way it\n    handles HTML: some parses and output might have different case than before. HTML is still case-insensitive,\n    of course, but Hickory minor version has been increased just in case. API and semantics are otherwise unchanged.\n\n- Released version 0.5.4.\n    * Fixed project dependencies so ClojureScript is moved to a dev-dependency.\n\n- Released version 0.5.3.\n    * Minor bug fix to accommodate ClojureScript's new type hinting support.\n\n- Released version 0.5.2.\n    * Updates the Clojurescript version to use the latest version of Clojurescript (0.0-1934).\n\n- Released version 0.5.1.\n    * Added `has-child` and `has-descendant` selectors. Be careful with `has-descendant`, as it must do a full subtree search on each node, which is not fast.\n\n- Released version 0.5.0.\n    * Now works in Clojurescript as well, huge thanks to [Julien Eluard](https://github.com/jeluard) for doing the heavy lifting on this.\n    * Reorganized parts of the API into more granular namespaces for better organization.\n    * Added functions to convert between Hiccup and Hickory format; note that this conversion is not always exact or roundtripable, and can cause a full HTML reparse.\n    * Added new selector, `element-child`, which selects element nodes that are the child of another element node.\n    * Numerous bug fixes and improvements.\n\n- Released version 0.4.1, which adds a number of new selectors and selector combinators, including `find-in-text`, `precede-adjacent`, `follow-adjacent`, `precede` and `follow`.\n\n- Released version 0.4.0. Adds the `hickory.select` namespace with many helpful functions for searching through hickory-format HTML documents for specific nodes.\n\n- Released version 0.3.0. Provides a more helpful error message when hickory-to-html has an error. Now requires Clojure 1.4.\n\n- Released version 0.2.3. Fixes a bug where hickory-to-html was not html-escaping the values of tag attributes.\n\n- Released version 0.2.2. Fixes a bug where hickory-to-html was improperly html-escaping the contents of script/style tags.\n\n- Released version 0.2.1. This version fixes bugs:\n    * hickory-to-html now properly escapes text nodes\n    * text nodes will now preserve whitespace correctly\n\n- Released version 0.2.0. This version adds a second parsed data\n  format, explained above. To support this, the API for `parse` and\n  `parse-fragment` has been changed to allow their return values to be\n  passed to functions `as-hiccup` or `as-hickory` to determine the\n  final format. Also added are zippers for both Hiccup and Hickory\n  formats.\n\n## License\n\nCopyright © 2012 David Santiago\n\nDistributed under the Eclipse Public License, the same as Clojure.\n"
  },
  {
    "path": "bb.edn",
    "content": "{:deps {io.github.borkdude/lein2deps {:git/url \"https://github.com/borkdude/lein2deps\"\n                                      :git/sha \"e26edeb114c9d88a5c4d3abb683306588fcaad13\"}}\n :tasks\n {:requires ([babashka.fs :as fs])\n\n  test:clj (shell \"lein test\")\n\n  test:cljs-npm-install (do (shell \"npm install -g karma-cli\")\n                            (shell \"npm install\"))\n  test:cljs (do\n              (fs/delete-tree \"cljs-test-runner-out\")\n              (apply clojure \"-M:test:cljs:cljs-test-runner\" \"--env\" \"chrome-headless\" *command-line-args*))\n  repl:cljs-node (shell \"rlwrap bb clojure -M:test:cljs -m cljs.main -re node\")\n  repl:cljs-browser (shell \"rlwrap bb clojure -M:test:cljs -m cljs.main\")\n\n  quickdoc {:extra-deps {io.github.borkdude/quickdoc {:git/tag \"v0.2.5\", :git/sha \"25784ca\"}}\n            :requires ([quickdoc.api :as api])\n            :task (api/quickdoc {:source-paths [\"src/clj\" \"src/cljs\" \"src/cljs\"]\n                                 :git/branch \"master\"\n                                 :github/repo \"https://github.com/clj-commons/hickory\"})}}\n\n }\n"
  },
  {
    "path": "deps.edn",
    "content": "{:paths [\"src/clj\" \"src/cljc\" \"src/cljs\"]\n :deps\n {org.clojure/clojure {:mvn/version \"1.11.1\"}\n  org.jsoup/jsoup {:mvn/version \"1.22.2\"}}\n :aliases\n {:test {:extra-paths [\"test/cljc\"]}\n  :cljs {:extra-deps {org.clojure/clojurescript {:mvn/version \"1.11.60\"}}}\n  :cljs-test-runner\n  {:extra-deps {olical/cljs-test-runner {:mvn/version \"3.8.0\"}\n                viebel/codox-klipse-theme {:mvn/version \"0.0.1\"}}\n   :extra-paths [\"cljs-test-runner-out\"]\n   :main-opts [\"-m\" \"cljs-test-runner.main\"\n               \"--env\" \"chrome-headless\"\n               \"--namespace-regex\" \"hickory.test.*\"]}}}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"devDependencies\": {\n    \"karma\": \"^6.4.1\",\n    \"karma-chrome-launcher\": \"^3.1.1\",\n    \"karma-cli\": \"^2.0.0\",\n    \"karma-cljs-test\": \"^0.1.0\"\n  }\n}\n"
  },
  {
    "path": "project.clj",
    "content": "(defproject org.clj-commons/hickory (or (System/getenv \"PROJECT_VERSION\") \"0.7.1\")\n  :description \"HTML as Data\"\n  :url \"https://github.com/clj-commons/hickory\"\n  :license {:name \"Eclipse Public License\"\n            :url  \"http://www.eclipse.org/legal/epl-v10.html\"}\n\n  :deploy-repositories [[\"clojars\" {:url \"https://repo.clojars.org\"\n                                    :username :env/clojars_username\n                                    :password :env/clojars_org_clj_commons_password\n                                    :sign-releases true}]]\n\n  :dependencies [[org.clojure/clojure \"1.11.1\"]\n                 [org.clojure/clojurescript \"1.11.60\" :scope \"provided\"]\n                 [org.jsoup/jsoup \"1.22.2\"]]\n\n  :source-paths [\"src/clj\" \"src/cljc\" \"src/cljs\"]\n\n  :profiles\n  {:dev  {:source-paths [\"src/clj\" \"src/cljc\"]}\n   :test {:source-paths [\"src/cljs\" \"src/cljc\" \"test/cljc\" \"test/cljs\"]}})\n"
  },
  {
    "path": "src/clj/hickory/core.clj",
    "content": "(ns hickory.core\n  (:require [hickory.utils :as utils]\n            [hickory.zip :as hzip]\n            [clojure.zip :as zip])\n  (:import [org.jsoup Jsoup]\n           [org.jsoup.nodes Attribute Attributes Comment DataNode Document\n            DocumentType Element TextNode XmlDeclaration]\n           [org.jsoup.parser Tag Parser]))\n\n(set! *warn-on-reflection* true)\n\n(defn- end-or-recur [as-fn loc data & [skip-child?]]\n  (let [new-loc (-> loc (zip/replace data) zip/next (cond-> skip-child? zip/next))]\n    (if (zip/end? new-loc)\n      (zip/root new-loc)\n      #(as-fn (zip/node new-loc) new-loc))))\n\n;;\n;; Protocols\n;;\n\n(defprotocol HiccupRepresentable\n  \"Objects that can be represented as Hiccup nodes implement this protocol in\n   order to make the conversion.\"\n  (as-hiccup [this] [this zip-loc]\n    \"Converts the node given into a hiccup-format data structure. The\n     node must have an implementation of the HiccupRepresentable\n     protocol; nodes created by parse or parse-fragment already do.\"))\n\n(defprotocol HickoryRepresentable\n  \"Objects that can be represented as HTML DOM node maps, similar to\n   clojure.xml, implement this protocol to make the conversion.\n\n   Each DOM node will be a map or string (for Text/CDATASections). Nodes that\n   are maps have the appropriate subset of the keys\n\n     :type     - [:comment, :document, :document-type, :element]\n     :tag      - node's tag, check :type to see if applicable\n     :attrs    - node's attributes as a map, check :type to see if applicable\n     :content  - node's child nodes, in a vector, check :type to see if\n                 applicable\"\n  (as-hickory [this] [this zip-loc]\n    \"Converts the node given into a hickory-format data structure. The\n     node must have an implementation of the HickoryRepresentable protocol;\n     nodes created by parse or parse-fragment already do.\"))\n\n\n(extend-protocol HiccupRepresentable\n  Attribute\n  ;; Note the attribute value is not html-escaped; see comment for Element.\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this _] [(utils/lower-case-keyword (.getKey this)) (.getValue this)]))\n  Attributes\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this _] (into {} (map as-hiccup this))))\n  Comment\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc] (end-or-recur as-hiccup loc (str \"<!--\" (.getData this) \"-->\"))))\n  DataNode\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc] (end-or-recur as-hiccup loc (str this))))\n  Document\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc] (end-or-recur as-hiccup loc (apply list (.childNodes this)))))\n  DocumentType\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc]\n     (end-or-recur as-hiccup loc (utils/render-doctype (.name this)\n                                                       (.publicId this)\n                                                       (.systemId this)))))\n  Element\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc]\n     ;; There is an issue with the hiccup format, which is that it\n     ;; can't quite cover all the pieces of HTML, so anything it\n     ;; doesn't cover is thrown into a string containing the raw\n     ;; HTML. This presents a problem because it is then never the case\n     ;; that a string in a hiccup form should be html-escaped (except\n     ;; in an attribute value) when rendering; it should already have\n     ;; any escaping. Since the HTML parser quite properly un-escapes\n     ;; HTML where it should, we have to go back and un-un-escape it\n     ;; wherever text would have been un-escaped. We do this by\n     ;; html-escaping the parsed contents of text nodes, and not\n     ;; html-escaping comments, data-nodes, and the contents of\n     ;; unescapable nodes.\n     (let [tag (utils/lower-case-keyword (.tagName this))\n           children (cond->> (.childNodes this) (utils/unescapable-content tag) (map str))\n           data (into [] (concat [tag (trampoline as-hiccup (.attributes this))] children))]\n       (end-or-recur as-hiccup loc data (utils/unescapable-content tag)))))\n  TextNode\n  ;; See comment for Element re: html escaping.\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc] (end-or-recur as-hiccup loc (utils/html-escape (.getWholeText this)))))\n  XmlDeclaration\n  (as-hiccup\n    ([this] (trampoline as-hiccup this (hzip/hiccup-zip this)))\n    ([this loc] (end-or-recur as-hiccup loc (str this)))))\n\n(extend-protocol HickoryRepresentable\n  Attribute\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this _] [(utils/lower-case-keyword (.getKey this)) (.getValue this)]))\n  Attributes\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this _] (not-empty (into {} (map as-hickory this)))))\n  Comment\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this loc] (end-or-recur as-hickory loc {:type :comment\n                                              :content [(.getData this)]} true)))\n  DataNode\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this loc] (end-or-recur as-hickory loc (str this))))\n  Document\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this loc] (end-or-recur as-hickory loc {:type :document\n                                              :content (or (seq (.childNodes this)) nil)})))\n  DocumentType\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this loc] (end-or-recur as-hickory loc {:type :document-type\n                                              :attrs (trampoline as-hickory (.attributes this))})))\n  Element\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this loc] (end-or-recur as-hickory loc {:type :element\n                                              :attrs (trampoline as-hickory (.attributes this))\n                                              :tag (utils/lower-case-keyword (.tagName this))\n                                              :content (or (seq (.childNodes this)) nil)})))\n  TextNode\n  (as-hickory\n    ([this] (trampoline as-hickory this (hzip/hickory-zip this)))\n    ([this loc] (end-or-recur as-hickory loc (.getWholeText this)))))\n\n(defn parse\n  \"Parse an entire HTML document into a DOM structure that can be\n   used as input to as-hiccup or as-hickory.\"\n  [s]\n  (cond (instance? String s)\n        (Jsoup/parse ^String s)\n        (instance? java.io.File s)\n        (Jsoup/parse ^java.io.File s)\n        (instance? java.nio.file.Path s)\n        (Jsoup/parse ^java.nio.file.Path s)\n        :else\n        (throw (ex-info \"Invalid input for parse\" {:type (type s)}))))\n\n(defn parse-fragment\n  \"Parse an HTML fragment (some group of tags that might be at home somewhere\n   in the tag hierarchy under <body>) into a list of DOM elements that can\n   each be passed as input to as-hiccup or as-hickory.\"\n  [s]\n  (into [] (Parser/parseFragment s (Element. (Tag/valueOf \"body\") \"\") \"\")))\n"
  },
  {
    "path": "src/cljc/hickory/convert.cljc",
    "content": "(ns hickory.convert\n  \"Functions to convert from one representation to another.\"\n  (:require [hickory.render :as render]\n            [hickory.core :as core]\n            [hickory.utils :as utils]))\n\n(defn hiccup-to-hickory\n  \"Given a sequence of hiccup forms representing a full document,\n   returns an equivalent hickory node representation of that document.\n   This will perform HTML5 parsing as a full document, no matter what\n   it is given.\n\n   Note that this function is heavyweight: it requires a full HTML\n   re-parse to work.\"\n  [hiccup-forms]\n  (core/as-hickory (core/parse (render/hiccup-to-html hiccup-forms))))\n\n(defn hiccup-fragment-to-hickory\n  \"Given a sequence of hiccup forms representing a document fragment,\n   returns an equivalent sequence of hickory fragments.\n\n   Note that this function is heavyweight: it requires a full HTML\n   re-parse to work.\"\n  [hiccup-forms]\n  (map core/as-hickory\n       (core/parse-fragment (render/hiccup-to-html hiccup-forms))))\n\n(defn hickory-to-hiccup\n  \"Given a hickory format dom object, returns an equivalent hiccup\n   representation. This can be done directly and exactly, but in general\n   you will not be able to go back from the hiccup.\"\n  [dom]\n  (if (string? dom)\n    (utils/html-escape dom)\n    (case (:type dom)\n      :document\n      (mapv hickory-to-hiccup (:content dom))\n      :document-type\n      (utils/render-doctype (get-in dom [:attrs :name])\n                            (get-in dom [:attrs :publicid])\n                            (get-in dom [:attrs :systemid]))\n      :element\n      (if (utils/unescapable-content (:tag dom))\n        (if (every? string? (:content dom))\n          ;; Merge :attrs contents with {} to prevent nil from getting into\n          ;; the hiccup forms when there are no attributes.\n          (apply vector (:tag dom) (into {} (:attrs dom)) (:content dom))\n          (throw (ex-info\n                  \"An unescapable content tag had non-string children.\"\n                  {:error-location dom})))\n        (apply vector (:tag dom) (into {} (:attrs dom))\n               (map hickory-to-hiccup (:content dom))))\n      :comment\n      (str \"<!--\" (apply str (:content dom)) \"-->\"))))\n\n"
  },
  {
    "path": "src/cljc/hickory/hiccup_utils.cljc",
    "content": "(ns hickory.hiccup-utils\n  \"Utilities for working with hiccup forms.\"\n  (:require [clojure.string :as str]))\n\n(defn- first-idx\n  \"Given two possible indexes, returns the lesser that is not -1. If both\n   are -1, then -1 is returned. Useful for searching strings for multiple\n   markers, as many routines will return -1 for not found.\n\n   Examples: (first-idx -1 -1) => -1\n             (first-idx -1 2) => 2\n             (first-idx 5 -1) => 5\n             (first-idx 5 3) => 3\"\n  #?(:clj  [^long a ^long b]\n     :cljs [a b])\n  (if (== a -1)\n    b\n    (if (== b -1)\n      a\n      (min a b))))\n\n(defn- index-of\n  ([^String s c]\n    #?(:clj  (.indexOf s (int c))\n       :cljs (.indexOf s c)))\n  ([^String s c idx]\n    #?(:clj  (.indexOf s (int c) (int idx))\n       :cljs (.indexOf s c idx))))\n\n(defn- split-keep-trailing-empty\n  \"clojure.string/split is a wrapper on java.lang.String/split with the limit\n   parameter equal to 0, which keeps leading empty strings, but discards\n   trailing empty strings. This makes no sense, so we have to write our own\n   to keep the trailing empty strings.\"\n  [s re]\n  (str/split s re -1))\n\n(defn tag-well-formed?\n  \"Given a hiccup tag element, returns true iff the tag is in 'valid' hiccup\n   format. Which in this function means:\n      1. Tag name is non-empty.\n      2. If there is an id, there is only one.\n      3. If there is an id, it is nonempty.\n      4. If there is an id, it comes before any classes.\n      5. Any class name is nonempty.\"\n  [tag-elem]\n  (let [tag-elem (name tag-elem)\n        hash-idx (int (index-of tag-elem \\#))\n        dot-idx (int (index-of tag-elem \\.))\n        tag-cutoff (first-idx hash-idx dot-idx)]\n    (and (< 0 (count tag-elem)) ;; 1.\n         (if (== tag-cutoff -1) true (> tag-cutoff 0)) ;; 1.\n         (if (== hash-idx -1)\n           true\n           (and (== -1 (index-of tag-elem \\# (inc hash-idx))) ;; 2.\n                (< (inc hash-idx) (first-idx (index-of tag-elem \\. ;; 3.\n                                                       (inc hash-idx))\n                                             (count tag-elem)))))\n         (if (and (not= hash-idx -1) (not= dot-idx -1)) ;; 4.\n           (< hash-idx dot-idx)\n           true)\n         (if (== dot-idx -1) ;; 5.\n           true\n           (let [classes (.substring tag-elem (inc dot-idx))]\n             (every? #(< 0 (count %))\n                     (split-keep-trailing-empty classes #\"\\.\")))))))\n\n(defn tag-name\n  \"Given a well-formed hiccup tag element, return just the tag name as\n  a string.\"\n  [tag-elem]\n  (let [tag-elem (name tag-elem)\n        hash-idx (int (index-of tag-elem \\#))\n        dot-idx (int (index-of tag-elem \\.))\n        cutoff (first-idx hash-idx dot-idx)]\n    (if (== cutoff -1)\n      ;; No classes or ids, so the entire tag-element is the name.\n      tag-elem\n      ;; There was a class or id, so the tag name ends at the first\n      ;; of those.\n      (.substring tag-elem 0 cutoff))))\n\n(defn class-names\n  \"Given a well-formed hiccup tag element, return a vector containing\n   any class names included in the tag, as strings. Ignores the hiccup\n   requirement that any id on the tag must come\n   first. Example: :div.foo.bar => [\\\"foo\\\" \\\"bar\\\"].\"\n  [tag-elem]\n  (let [tag-elem (name tag-elem)]\n    (loop [curr-dot (index-of tag-elem \\.)\n           classes (transient [])]\n      (if (== curr-dot -1)\n        ;; Didn't find another dot, so no more classes.\n        (persistent! classes)\n        ;; There's another dot, so there's another class.\n        (let [next-dot (index-of tag-elem \\. (inc curr-dot))\n              next-hash (index-of tag-elem \\# (inc curr-dot))\n              cutoff (first-idx next-dot next-hash)]\n          (if (== cutoff -1)\n            ;; Rest of the tag element is the last class.\n            (recur next-dot\n                   (conj! classes (.substring tag-elem (inc curr-dot))))\n            ;; The current class name is terminated by another element.\n            (recur next-dot\n                   (conj! classes\n                          (.substring tag-elem (inc curr-dot) cutoff)))))))))\n\n(defn id\n  \"Given a well-formed hiccup tag element, return a string containing\n   the id, or nil if there isn't one.\"\n  [tag-elem]\n  (let [tag-elem (name tag-elem)\n        hash-idx (int (index-of tag-elem \\#))\n        next-dot-idx (int (index-of tag-elem \\. hash-idx))]\n    (if (== hash-idx -1)\n      nil\n      (if (== next-dot-idx -1)\n        (.substring tag-elem (inc hash-idx))\n        (.substring tag-elem (inc hash-idx) next-dot-idx)))))\n\n(defn- expand-content-seqs\n  \"Given a sequence of hiccup forms, presumably the content forms of another\n   hiccup element, return a new sequence with any sequence elements expanded\n   into the main sequence. This logic does not apply recursively, so sequences\n   inside sequences won't be expanded out. Also note that this really only\n   applies to sequences; things that seq? returns true on. So this excludes\n   vectors.\n     (expand-content-seqs [1 '(2 3) (for [x [1 2 3]] (* x 2)) [5]])\n     ==> (1 2 3 2 4 6 [5])\"\n  [content]\n  (loop [remaining-content content\n         result (transient [])]\n    (if (nil? remaining-content)\n      (persistent! result)\n      (if (seq? (first remaining-content))\n        (recur (next remaining-content)\n               ;; Fairly unhappy with this nested loop, but it seems\n               ;; necessary to continue the handling of transient vector.\n               (loop [remaining-seq (first remaining-content)\n                      result result]\n                 (if (nil? remaining-seq)\n                   result\n                   (recur (next remaining-seq)\n                          (conj! result (first remaining-seq))))))\n        (recur (next remaining-content)\n               (conj! result (first remaining-content)))))))\n\n(defn- normalize-element\n  \"Given a well-formed hiccup form, ensure that it is in the form\n     [tag attributes content1 ... contentN].\n   That is, an unadorned tag name (keyword, lowercase), all attributes in the\n   attribute map in the second element, and then any children. Note that this\n   does not happen recursively; content is not modified.\"\n  [hiccup-form]\n  (let [[tag-elem & content] hiccup-form]\n    (when (not (tag-well-formed? tag-elem))\n      (throw (ex-info (str \"Invalid input: Tag element\"\n                           tag-elem \"is not well-formed.\")\n                      {})))\n    (let [tag-name (keyword (str/lower-case (tag-name tag-elem)))\n          tag-classes (class-names tag-elem)\n          tag-id (id tag-elem)\n          tag-attrs {:id tag-id\n                     :class (if (not (empty? tag-classes))\n                              (str/join \" \" tag-classes))}\n          [map-attrs content] (if (map? (first content))\n                                [(first content) (rest content)]\n                                [nil content])\n          ;; Note that we replace tag attributes with map attributes, without\n          ;; merging them. This is to match hiccup's behavior.\n          attrs (merge tag-attrs map-attrs)]\n      (apply vector tag-name attrs content))))\n\n(defn normalize-form\n  \"Given a well-formed hiccup form, recursively normalizes it, so that it and\n   all children elements will also be normalized. A normalized form is in the\n   form\n     [tag attributes content1 ... contentN].\n   That is, an unadorned tag name (keyword, lowercase), all attributes in the\n   attribute map in the second element, and then any children. Any content\n   that is a sequence is also expanded out into the main sequence of content\n   items.\"\n  [form]\n  (if (string? form)\n    form\n    ;; Do a pre-order walk and save the first two items, then do the children,\n    ;; then glue them back together.\n    (let [[tag attrs & contents] (normalize-element form)]\n      (apply vector tag attrs (map #(if (vector? %)\n                                      ;; Recurse only on vec children.\n                                      (normalize-form %)\n                                      %)\n                                   (expand-content-seqs contents))))))\n"
  },
  {
    "path": "src/cljc/hickory/render.cljc",
    "content": "(ns hickory.render\n  (:require [hickory.hiccup-utils :as hu]\n            [hickory.utils :as utils]\n            [clojure.string :as str]))\n\n;;\n;; Hickory to HTML\n;;\n\n(defn- render-hickory-attribute\n  \"Given a map entry m, representing the attribute name and value, returns a\n   string representing that key/value pair as it would be rendered into HTML.\"\n  [m]\n  (str \" \" (name (key m)) \"=\\\"\" (utils/html-escape (val m)) \"\\\"\"))\n\n(defn hickory-to-html\n  \"Given a hickory HTML DOM map structure (as returned by as-hickory), returns a\n   string containing HTML it represents. Keep in mind this function is not super\n   fast or heavy-duty.\n\n   Note that it will NOT in general be the case that\n\n     (= my-html-src (hickory-to-html (as-hickory (parse my-html-src))))\n\n   as we do not keep any letter case or whitespace information, any\n   \\\"tag-soupy\\\" elements, attribute quote characters used, etc.\"\n  [dom]\n  (if (string? dom)\n    (utils/html-escape dom)\n    (try\n      (case (:type dom)\n        :document\n        (apply str (map hickory-to-html (:content dom)))\n        :document-type\n        (utils/render-doctype (get-in dom [:attrs :name])\n                              (get-in dom [:attrs :publicid])\n                              (get-in dom [:attrs :systemid]))\n        :element\n        (cond\n         (utils/void-element (:tag dom))\n         (str \"<\" (name (:tag dom))\n              (apply str (map render-hickory-attribute (:attrs dom)))\n              \">\")\n         (utils/unescapable-content (:tag dom))\n         (str \"<\" (name (:tag dom))\n              (apply str (map render-hickory-attribute (:attrs dom)))\n              \">\"\n              (apply str (:content dom)) ;; Won't get html-escaped.\n              \"</\" (name (:tag dom)) \">\")\n         :else\n         (str \"<\" (name (:tag dom))\n              (apply str (map render-hickory-attribute (:attrs dom)))\n              \">\"\n              (apply str (map hickory-to-html (:content dom)))\n              \"</\" (name (:tag dom)) \">\"))\n        :comment\n        (str \"<!--\" (apply str (:content dom)) \"-->\"))\n      (catch #?(:clj  IllegalArgumentException\n                :cljs js/Error) e\n        (throw\n          (if (utils/starts-with #?(:clj (.getMessage e) :cljs (.-message e)) \"No matching clause: \")\n            (ex-info (str \"Not a valid node: \" (pr-str dom)) {:dom dom})\n            e))))))\n\n;;\n;; Hiccup to HTML\n;;\n\n(defn- render-hiccup-attrs\n  \"Given a hiccup attribute map, returns a string containing the attributes\n   rendered as they should appear in an HTML tag, right after the tag (including\n   a leading space to separate from the tag, if any attributes present).\"\n  [attrs]\n  ;; Hiccup normally does not html-escape strings, but it does for attribute\n  ;; values.\n  (let [attrs-str (->> (for [[k v] attrs]\n                         (cond (true? v)\n                               (str (name k))\n                               (nil? v)\n                               \"\"\n                               :else\n                               (str (name k) \"=\" \"\\\"\" (utils/html-escape v) \"\\\"\")))\n                       (filter #(not (empty? %)))\n                       sort\n                       (str/join \" \"))]\n    (if (not (empty? attrs-str))\n      ;; If the attrs-str is not \"\", we need to pad the front so that the\n      ;; tag will separate from the attributes. Otherwise, \"\" is fine to return.\n      (str \" \" attrs-str)\n      attrs-str)))\n\n(declare hiccup-to-html)\n(defn- render-hiccup-element\n  \"Given a normalized hiccup element (such as the output of\n   hickory.hiccup-utils/normalize-form; see this function's docstring\n   for more detailed definition of a normalized hiccup element), renders\n   it to HTML and returns it as a string.\"\n  [n-element]\n  (let [[tag attrs & content] n-element]\n    (if (utils/void-element tag)\n      (str \"<\" (name tag) (render-hiccup-attrs attrs) \">\")\n      (str \"<\" (name tag) (render-hiccup-attrs attrs) \">\"\n           (hiccup-to-html content)\n           \"</\" (name tag) \">\"))))\n\n(defn- render-hiccup-form\n  \"Given a normalized hiccup form (such as the output of\n   hickory.hiccup-utils/normalize-form; see this function's docstring\n   for more detailed definition of a normalized hiccup form), renders\n   it to HTML and returns it as a string.\"\n  [n-form]\n  (if (vector? n-form)\n    (render-hiccup-element n-form)\n    n-form))\n\n(defn hiccup-to-html\n  \"Given a sequence of hiccup forms (as returned by as-hiccup), returns a\n   string containing HTML it represents. Keep in mind this function is not super\n   fast or heavy-duty, and definitely not a replacement for dedicated hiccup\n   renderers, like hiccup itself, which *is* fast and heavy-duty.\n\n```klipse\n  (hiccup-to-html '([:html {} [:head {}] [:body {} [:a {} \\\"foo\\\"]]]))\n```\n\n   Note that it will NOT in general be the case that\n\n     (= my-html-src (hiccup-to-html (as-hiccup (parse my-html-src))))\n\n   as we do not keep any letter case or whitespace information, any\n   \\\"tag-soupy\\\" elements, attribute quote characters used, etc. It will also\n   not generally be the case that this function's output will exactly match\n   hiccup's.\n   For instance:\n\n```klipse\n(hiccup-to-html (as-hiccup (parse \\\"<A href=\\\\\\\"foo\\\\\\\">foo</A>\\\")))\n```\n  \"\n  [hiccup-forms]\n  (apply str (map #(render-hiccup-form (hu/normalize-form %)) hiccup-forms)))\n\n"
  },
  {
    "path": "src/cljc/hickory/select.cljc",
    "content": "(ns hickory.select\n  \"Functions to query hickory-format HTML data.\n\n   See clojure.zip for more information on zippers, locs, nodes, next, etc.\"\n  (:require [clojure.zip :as zip]\n            [clojure.string :as string]\n            [hickory.zip :as hzip])\n  #?(:clj\n     (:import clojure.lang.IFn))\n  (:refer-clojure :exclude [and or not class]))\n\n;;\n;; Utilities\n;;\n\n(defn until\n  \"Calls f on val until pred called on the result is true. If not, it\n   repeats by calling f on the result, etc. The value that made pred\n   return true is returned.\"\n  [f val pred]\n  (let [next-val (f val)]\n    (if (pred next-val)\n      next-val\n      (recur f next-val pred))))\n\n(defn count-until\n  \"Calls f on val until pred called on the result is true. If not, it\n   repeats by calling f on the result, etc. The count of times this\n   process was repeated until pred returned true is returned.\"\n  [f val pred]\n  (loop [next-val val\n         cnt 0]\n    (if (pred next-val)\n      cnt\n      (recur (f next-val) (inc cnt)))))\n\n(defn next-pred\n  \"Like clojure.zip/next, but moves until it reaches a node that returns\n   true when the function in the pred argument is called on them, or reaches\n   the end.\"\n  [hzip-loc pred]\n  (until zip/next hzip-loc #(clojure.core/or (zip/end? %)\n                                             (pred %))))\n\n(defn prev-pred\n  \"Like clojure.zip/prev, but moves until it reaches a node that returns\n   true when the function in the pred argument is called on them, or reaches\n   the beginning.\"\n  [hzip-loc pred]\n  (until zip/prev hzip-loc #(clojure.core/or (nil? %)\n                                             (pred %))))\n\n(defn left-pred\n  \"Like clojure.zip/left, but moves until it reaches a node that returns\n   true when the function in the pred argument is called on them, or reaches\n   the left boundary of the current group of siblings.\"\n  [hzip-loc pred]\n  (until zip/left hzip-loc #(clojure.core/or (nil? %)\n                                             (pred %))))\n\n(defn right-pred\n  \"Like clojure.zip/right, but moves until it reaches a node that returns\n   true when the function in the pred argument is called on them, or reaches\n   the right boundary of the current group of siblings.\"\n  [hzip-loc pred]\n  (until zip/right hzip-loc #(clojure.core/or (nil? %)\n                                              (pred %))))\n\n(defn up-pred\n  \"Like clojure.zip/up, but moves until it reaches a node that returns\n   true when the function in the pred argument is called on them, or reaches\n   the beginning.\"\n  [hzip-loc pred]\n  (until zip/up hzip-loc #(clojure.core/or (nil? %)\n                                           (pred %))))\n\n(defn next-of-node-type\n  \"Like clojure.zip/next, but only counts moves to nodes that have\n   the given type.\"\n  [hzip-loc node-type]\n  (next-pred hzip-loc #(= node-type (:type (zip/node %)))))\n\n(defn prev-of-node-type\n  \"Like clojure.zip/prev, but only counts moves to nodes that have\n   the given type.\"\n  [hzip-loc node-type]\n  (prev-pred hzip-loc #(= node-type (:type (zip/node %)))))\n\n(defn left-of-node-type\n  \"Like clojure.zip/left, but only counts moves to nodes that have\n   the given type.\"\n  [hzip-loc node-type]\n  (left-pred hzip-loc #(= node-type (:type (zip/node %)))))\n\n(defn right-of-node-type\n  \"Like clojure.zip/right, but only counts moves to nodes that have\n   the given type.\"\n  [hzip-loc node-type]\n  (right-pred hzip-loc #(= node-type (:type (zip/node %)))))\n\n(defn after-subtree\n  \"Given a zipper loc, returns the zipper loc that is the first one after\n   the arg's subtree, if there is a subtree. If there is no loc after this\n   loc's subtree, returns the end node.\"\n  [zip-loc]\n  (if (zip/end? zip-loc)\n    zip-loc\n    (clojure.core/or (zip/right zip-loc)\n                     (loop [curr-loc zip-loc]\n                       (if (zip/up curr-loc)\n                         (clojure.core/or (zip/right (zip/up curr-loc))\n                                          (recur (zip/up curr-loc)))\n                         [(zip/node curr-loc) :end])))))\n\n;;\n;; Select\n;;\n\n(defn select-next-loc\n  \"Given a selector function and a loc inside a hickory zip data structure,\n   returns the next zipper loc that satisfies the selection function. This can\n   be the loc that is passed in, so be sure to move to the next loc if you\n   want to use this function to exhaustively search through a tree manually.\n   Note that if there is no next node that satisfies the selection function, nil\n   is returned.\n\n   The third argument, if present, must be a function of one argument that is\n   called on a zipper loc to return the next loc to consider in the search. By\n   default, this argument is zip/next. The fourth argument, if present, must be\n   a function of one argument that is called on a zipper loc to determine if\n   the end of the search has been reached (true return value). When the fourth\n   argument returns true on a loc, that loc is not considered in the search and\n   the search finishes with a nil return. By default, the fourth argument is\n   zip/end?.\"\n  ([selector-fn hzip-loc]\n     (select-next-loc selector-fn hzip-loc zip/next))\n  ([selector-fn hzip-loc next-fn]\n     (select-next-loc selector-fn hzip-loc next-fn zip/end?))\n  ([selector-fn hzip-loc next-fn end?-fn]\n     (loop [loc hzip-loc]\n       (if (end?-fn loc)\n         nil\n         (if (selector-fn loc)\n           loc\n           (recur (next-fn loc)))))))\n\n(defn select-locs\n  \"Given a selector function and a hickory data structure, returns a vector\n   containing all of the zipper locs selected by the selector function.\"\n  [selector-fn hickory-tree]\n  (loop [loc (select-next-loc selector-fn\n                              (hzip/hickory-zip hickory-tree))\n         selected-nodes (transient [])]\n    (if (nil? loc)\n      (persistent! selected-nodes)\n      (recur (select-next-loc selector-fn (zip/next loc))\n             (conj! selected-nodes loc)))))\n\n(defn select\n  \"Given a selector function and a hickory data structure, returns a vector\n   containing all of the hickory nodes selected by the selector function.\"\n  [selector-fn hickory-tree]\n  (mapv zip/node (select-locs selector-fn hickory-tree)))\n\n;;\n;; Selectors\n;;\n;; Mostly based off the spec at http://www.w3.org/TR/selectors/#selectors\n;; Some selectors are simply not possible outside a browser (active,\n;; visited, etc).\n;;\n\n(defn node-type\n  \"Return a function that takes a zip-loc argument and returns the\n   zip-loc passed in iff it has the given node type. The type\n   argument can be a String or Named (keyword, symbol). The node type\n   comparison is done case-insensitively.\"\n  [type]\n  (fn [hzip-loc]\n    (let [node (zip/node hzip-loc)\n          node-type (-> node :type)]\n      (if (clojure.core/and node-type\n                            (= (string/lower-case (name node-type))\n                               (string/lower-case (name type))))\n        hzip-loc))))\n\n(defn tag\n  \"Return a function that takes a zip-loc argument and returns the\n   zip-loc passed in iff it has the given tag. The tag argument can be\n   a String or Named (keyword, symbol). The tag name comparison\n   is done case-insensitively.\"\n  [tag]\n  (fn [hzip-loc]\n    (let [node (zip/node hzip-loc)\n          node-tag (-> node :tag)]\n      (if (clojure.core/and node-tag\n                            (= (string/lower-case (name node-tag))\n                               (string/lower-case (name tag))))\n        hzip-loc))))\n\n(defn attr\n  \"Returns a function that takes a zip-loc argument and returns the\n   zip-loc passed in iff it has the given attribute, and that attribute\n   optionally satisfies a predicate given as an additional argument. With\n   a single argument, the attribute name (a string, keyword, or symbol),\n   the function returned will return the zip-loc if that attribute is\n   present (and has any value) on the zip-loc's node. The attribute name\n   will be compared case-insensitively, but the attribute value (if present),\n   will be passed as-is to the predicate.\n\n   If the predicate argument is given, it will only return the zip-loc if\n   that predicate is satisfied when given the attribute's value as its only\n   argument. Note that the predicate only gets called when the attribute is\n   present, so it can assume its argument is not nil.\"\n  ([attr-name]\n     ;; Since we want this call to succeed in any case where this attr\n     ;; is present, we pass in a function that always returns true.\n     (attr attr-name (fn [_] true)))\n  ([attr-name predicate]\n     ;; Note that attribute names are normalized to lowercase by\n     ;; jsoup, as an html5 parser should; see here:\n     ;; http://www.whatwg.org/specs/web-apps/current-work/#attribute-name-state\n     (fn [hzip-loc]\n       (let [node (zip/node hzip-loc)\n             attr-key (keyword (string/lower-case (name attr-name)))]\n         ;; If the attribute does not exist, we'll definitely return null.\n         ;; Otherwise, we'll ask the predicate if we should return hzip-loc.\n         (if (clojure.core/and (contains? (:attrs node) attr-key)\n                               (predicate (get-in node [:attrs attr-key])))\n           hzip-loc)))))\n\n(defn id\n  \"Returns a function that takes a zip-loc argument and returns the\n   zip-loc passed in iff it has the given id. The id argument can be\n   a String or Named (keyword, symbol). The id name comparison\n   is done case-insensitively.\"\n  [id]\n  (attr :id #(= (string/lower-case %)\n                (string/lower-case (name id)))))\n\n(defn class\n  \"Returns a function that takes a zip-loc argument and returns the\n   zip-loc passed in iff it has the given class. The class argument can\n   be a String or Named (keyword, symbol). The class name comparison\n   is done case-insensitively.\"\n  [class-name]\n  (letfn [(parse-classes [class-str]\n                       (into #{} (mapv string/lower-case\n                                       (string/split class-str #\"\\s+\"))))]\n    (attr :class #(contains? (parse-classes %)\n                             (string/lower-case (name class-name))))))\n\n(defn any\n  \"This selector takes no args, it simply is the selector function. It returns\n   true on any element it is called on; corresponds to the CSS '*' selector.\"\n  [hzip-loc]\n  (if (= :element (-> (zip/node hzip-loc) :type))\n    hzip-loc))\n\n(def element\n  \"Another name for the any selector, to express that it can be used to only\n   select elements.\"\n  any)\n\n(defn element-child\n  \"This selector takes no args, it simply is the selector function. It returns\n   the zip-loc passed in iff that loc is an element, and it has a parent\n   that is also an element.\"\n  [hzip-loc]\n  (let [possible-parent (zip/up hzip-loc)]\n    (clojure.core/and (element hzip-loc)\n                      ;; Check that we are not at the top already first.\n                      possible-parent\n                      (element possible-parent))))\n\n(defn root\n  \"This selector takes no args, it simply is the selector function. It returns\n   the zip-loc of the root node (the HTML element).\"\n  [hzip-loc]\n  (if (= :html (-> (zip/node hzip-loc) :tag))\n    hzip-loc))\n\n(defn find-in-text\n  \"Returns a function that takes a zip-loc argument and returns the zip-loc\n   passed in iff it has some text node in its contents that matches the regular\n   expression. Note that this only applies to the direct text content of a node;\n   nodes which have the given text in one of their child nodes will not be\n   selected.\"\n  [re]\n  (fn [hzip-loc]\n    (when (some #(re-find re %)\n                (->> (zip/node hzip-loc)\n                     :content\n                     (filter string?)))\n      hzip-loc)))\n\n(defn n-moves-until\n  \"This selector returns a selector function that selects its argument if\n   that argument is some \\\"distance\\\" from a \\\"boundary.\\\" This is an abstract\n   way of phrasing it, but it captures the full generality.\n\n   The selector this function returns will apply the move argument to its own\n   output, beginning with its zipper loc argument, until the term-pred argument\n   called on its output returns true. At that point, the number of times the\n   move function was called successfully is compared to kn+c; if there exists\n   some value of k such that the two quantities are equal, then the selector\n   will return the argument zipper loc successfully.\n\n   For example, (n-moves-until 2 1 clojure.zip/left nil?) will return a selector\n   that calls zip/left on its own output, beginning with the argument zipper\n   loc, until its return value is nil (nil? returns true). Suppose it called\n   left 5 times before zip/left returned nil. Then the selector will return\n   with success, since 2k+1 = 5 for k = 2.\n\n   Most nth-child-* selectors in this package use n-moves-until in their\n   implementation.\"\n  [n c move term-pred]\n  (fn [hzip-loc]\n    (let [distance (count-until move hzip-loc term-pred)]\n      (if (== 0 n)\n        ;; No stride, so distance must = c to select.\n        (if (== distance c)\n          hzip-loc)\n        ;; There's a stride, so need to subtract c and see if the\n        ;; remaining distance is a multiple of n.\n        (if (== 0 (rem (- distance c) n))\n          hzip-loc)))))\n\n(defn nth-of-type\n  \"Returns a function that returns true if the node is the nth child of\n   its parent (and it has a parent) of the given tag type. First element is 1,\n   last is n.\"\n  ([c typ]\n     (cond (= :odd c)\n           (nth-of-type 2 1 typ)\n           (= :even c)\n           (nth-of-type 2 0 typ)\n           :else\n           (nth-of-type 0 c typ)))\n  ([n c typ]\n     (fn [hzip-loc]\n       ;; We're only interested in elements whose parents are also elements,\n       ;; so check this up front and maybe save some work.\n       (if (clojure.core/and (element-child hzip-loc)\n                             (= typ (:tag (zip/node hzip-loc))))\n         (let [sel (n-moves-until n c\n                                  #(left-pred % (fn [x] (-> (zip/node x)\n                                                            :tag\n                                                            (= typ))))\n                                  nil?)]\n           (sel hzip-loc))))))\n\n(defn nth-last-of-type\n  \"Returns a function that returns true if the node is the nth last child of\n   its parent (and it has a parent) of the given tag type. First element is 1,\n   last is n.\"\n  ([c typ]\n     (cond (= :odd c)\n           (nth-last-of-type 2 1 typ)\n           (= :even c)\n           (nth-last-of-type 2 0 typ)\n           :else\n           (nth-last-of-type 0 c typ)))\n  ([n c typ]\n     (fn [hzip-loc]\n       ;; We're only interested in elements whose parents are also elements,\n       ;; so check this up front and maybe save some work.\n       (if (clojure.core/and (element-child hzip-loc)\n                             (= typ (:tag (zip/node hzip-loc))))\n         (let [sel (n-moves-until n c\n                                  #(right-pred % (fn [x] (-> (zip/node x)\n                                                             :tag\n                                                             (= typ))))\n                                  nil?)]\n           (sel hzip-loc))))))\n\n(defn nth-child\n  \"Returns a function that returns true if the node is the nth child of\n   its parent (and it has a parent). First element is 1, last is n.\"\n  ([c]\n     (cond (= :odd c)\n           (nth-child 2 1)\n           (= :even c)\n           (nth-child 2 0)\n           :else\n           (nth-child 0 c)))\n  ([n c]\n     (fn [hzip-loc]\n       ;; We're only interested in elements whose parents are also elements,\n       ;; so check this up front and maybe save some work.\n       (if (element-child hzip-loc)\n         (let [sel (n-moves-until n c #(left-of-node-type % :element) nil?)]\n           (sel hzip-loc))))))\n\n\n(defn nth-last-child\n  \"Returns a function that returns true if the node has n siblings after it,\n   and has a parent.\"\n  ([c]\n     (cond (= :odd c)\n           (nth-last-child 2 1)\n           (= :even c)\n           (nth-last-child 2 0)\n           :else\n           (nth-last-child 0 c)))\n  ([n c]\n     (fn [hzip-loc]\n       ;; We're only interested in elements whose parents are also elements,\n       ;; so check this up front and maybe save some work.\n       (if (element-child hzip-loc)\n         (let [sel (n-moves-until n c #(right-of-node-type % :element) nil?)]\n           (sel hzip-loc))))))\n\n(defn first-child\n  \"This selector takes no args, it is simply the selector. Returns\n   true if the node is the first child of its parent (and it has a\n   parent).\"\n  [hzip-loc]\n  (clojure.core/and (element-child hzip-loc)\n                    ((nth-child 1) hzip-loc)))\n\n(defn last-child\n  \"This selector takes no args, it is simply the selector. Returns\n   true if the node is the last child of its parent (and it has a\n   parent.\"\n  [hzip-loc]\n  (clojure.core/and (element-child hzip-loc)\n                    ((nth-last-child 1) hzip-loc)))\n\n;;\n;; Selector combinators\n;;\n\n(defn and\n  \"Takes any number of selectors and returns a selector that is true if\n   all of the argument selectors are true.\"\n  [& selectors]\n  (fn [zip-loc]\n    (if (every? #(% zip-loc) selectors)\n      zip-loc)))\n\n(defn or\n  \"Takes any number of selectors and returns a selector that is true if\n   any of the argument selectors are true.\"\n  [& selectors]\n  (fn [zip-loc]\n    (if (some #(% zip-loc) selectors)\n      zip-loc)))\n\n(defn not\n  \"Takes a selector argument and returns a selector that is true if\n   the underlying selector is false on its argument, and vice versa.\"\n  [selector]\n  (fn [hzip-loc]\n    (if (clojure.core/not (selector hzip-loc))\n      hzip-loc)))\n\n(defn el-not\n  \"Takes a selector argument and returns a selector that is true if\n   the underlying selector is false on its argument and vice versa, and\n   additionally that argument is an element node. Compared to the 'not'\n   selector, this corresponds more closely to the CSS equivalent, which\n   will only ever select elements.\"\n  [selector]\n  (and (node-type :element)\n       (not selector)))\n\n(defn compose-unary\n  \"Takes a unary selection function and any number of selectors and returns\n   a selector which returns true when each selector and the unary function\n   applied to each subsequenct selector returns true.\n\n   Example:\n   (compose-unary has-child (tag :div) (class :foo) (attr :disabled))\n   Produces the equivalent of:\n   (and (tag :div)\n        (has-child (and (class :foo)\n                        (has-child (and (attr :disabled))))))\"\n  [unary-selector-fn & selectors]\n  (let [rev (reverse selectors)]\n    (loop [selectors (rest rev)\n           output (and (first rev))]\n      (cond\n        (empty? selectors) output\n        (= (count selectors) 1) (and (first selectors) (unary-selector-fn output))\n        :else (recur (rest selectors)\n                     (and (first selectors) (unary-selector-fn output)))))))\n\n(defn ordered-adjacent\n  \"Takes a zipper movement function and any number of selectors as arguments\n   and returns a selector that returns true when the zip-loc given as the\n   argument is satisfied by the first selector, and the zip-loc arrived at by\n   applying the move-fn argument is satisfied by the second selector, and so\n   on for all the selectors given as arguments. If the move-fn\n   moves to nil before the full selector list is satisfied, the entire\n   selector fails, but note that success is checked before a move to nil is\n   checked, so satisfying the last selector with the last node you can move\n   to succeeds.\"\n  [move-fn & selectors]\n  ;; We'll work backwards through the selector list with an index. First we'll\n  ;; build the selector list into an array for quicker access. We'll do it\n  ;; immediately and then closure-capture the result, so it does not get\n  ;; redone every time the selector is called.\n  (let [selectors (into-array IFn selectors)]\n    (fn [hzip-loc]\n      (loop [curr-loc hzip-loc\n             idx 0]\n        (cond (>= idx (count selectors))\n              hzip-loc ;; Got to end satisfying selectors, return the loc.\n              (nil? curr-loc)\n              nil ;; Ran off a boundary before satisfying selectors, return nil.\n              :else\n              (if-let [next-loc ((nth selectors idx) curr-loc)]\n                (recur (move-fn next-loc)\n                       (inc idx))))))))\n\n(defn child\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the end of\n   a chain of direct child relationships specified by the selectors given as\n   arguments.\n\n   Example: (child (tag :div) (class :foo) (attr :disabled))\n     will select the input in\n   <div><span class=\\\"foo\\\"><input disabled></input></span></div>\n     but not in\n   <div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n  [& selectors]\n  (apply ordered-adjacent zip/up (reverse selectors)))\n\n(defn has-child\n  \"Takes a selector as argument and returns a selector that returns true\n   when some direct child node of the zip-loc given as the argument satisfies\n   the selector.\n\n   Example: (has-child (tag :div))\n     will select only the inner span in\n   <div><span><div></div></span></div>\"\n  [selector]\n  (fn [hzip-loc]\n    (let [subtree-start-loc (-> hzip-loc zip/down)\n          has-children? (not= nil subtree-start-loc)]\n      ;; has-children? is needed to guard against zip/* receiving a nil arg in\n      ;; a selector.\n      (if has-children?\n        (if (select-next-loc selector subtree-start-loc\n                             zip/right\n                             #(nil? %))\n          hzip-loc)))))\n\n(defn parent\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the start of\n   a chain of direct child relationships specified by the selectors given\n   as arguments.\n\n   Example: (parent (tag :div) (class :foo) (attr :disabled))\n     will select the div in\n   <div><span class=\\\"foo\\\"><input disabled></input></span></div>\n     but not in\n   <div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n  [& selectors]\n  (apply compose-unary has-child selectors))\n\n(defn follow-adjacent\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the end of\n   a chain of direct element sibling relationships specified by the selectors\n   given as arguments.\n\n   Example: (follow-adjacent (tag :div) (class :foo))\n     will select the span in\n   <div>...</div><span class=\\\"foo\\\">...</span>\n     but not in\n   <div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n  [& selectors]\n  (apply ordered-adjacent\n         #(left-of-node-type % :element)\n         (reverse selectors)))\n\n(defn precede-adjacent\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the beginning of\n   a chain of direct element sibling relationships specified by the selectors\n   given as arguments.\n\n   Example: (precede-adjacent (tag :div) (class :foo))\n     will select the div in\n   <div>...</div><span class=\\\"foo\\\">...</span>\n     but not in\n   <div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n  [& selectors]\n  (apply ordered-adjacent\n         #(right-of-node-type % :element)\n         selectors))\n\n(defn ordered\n  \"Takes a zipper movement function and any number of selectors as arguments\n   and returns a selector that returns true when the zip-loc given as the\n   argument is satisfied by the first selector, and some zip-loc arrived at by\n   applying the move-fn argument *one or more times* is satisfied by the second\n   selector, and so on for all the selectors given as arguments. If the move-fn\n   moves to nil before a the full selector list is satisfied, the entire\n   selector fails, but note that success is checked before a move to nil is\n   checked, so satisfying the last selector with the last node you can move\n   to succeeds.\"\n  [move-fn & selectors]\n  ;; This function is a lot like ordered-adjacent, above, but:\n  ;; 1) failing to fulfill a selector does not stop us moving along the tree\n  ;; 2) therefore, we need to make sure the first selector matches the loc under\n  ;;    consideration, and not merely one that is farther along the movement\n  ;;    direction.\n  (let [selectors (into-array IFn selectors)]\n    (fn [hzip-loc]\n      ;; First need to check that the first selector matches the current loc,\n      ;; or else we can return nil immediately.\n      (let [fst-selector (nth selectors 0)]\n        (if (fst-selector hzip-loc)\n          ;; First selector matches this node, so now check along the\n          ;; movement direction for the rest of the selectors.\n          (loop [curr-loc (move-fn hzip-loc)\n                 idx 1]\n            (cond (>= idx (count selectors))\n                  hzip-loc ;; Satisfied all selectors, so return the orig. loc.\n                  (nil? curr-loc)\n                  nil ;; Ran out of movements before selectors, return nil.\n                  :else\n                  (if ((nth selectors idx) curr-loc)\n                    (recur (move-fn curr-loc)\n                           (inc idx))\n                    ;; Failed, so move but retry the same selector\n                    (recur (move-fn curr-loc) idx)))))))))\n\n(defn descendant\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the end of\n   a chain of descendant relationships specified by the\n   selectors given as arguments. To be clear, the node selected matches\n   the final selector, but the previous selectors can match anywhere in\n   the node's ancestry, provided they match in the order they are given\n   as arguments, from top to bottom.\n\n   Example: (descendant (tag :div) (class :foo) (attr :disabled))\n     will select the input in both\n   <div><span class=\\\"foo\\\"><input disabled></input></span></div>\n     and\n   <div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n  [& selectors]\n  (apply ordered zip/up (reverse selectors)))\n\n(defn has-descendant\n  \"Takes a selector as argument and returns a selector that returns true\n   when some descendant node of the zip-loc given as the argument satisfies\n   the selector.\n\n   Be aware that because this selector must do a full sub-tree search on\n   each node examined, it can have terrible performance. It's helpful if this is\n   a late clause in an `and`, to prevent it from even attempting to match\n   unless other criteria have been met first.\n\n   Example: (has-descendant (tag :div))\n     will select the span and the outer div, but not the inner div, in\n   <span><div><div></div></div></span>\"\n  [selector]\n  (fn [hzip-loc]\n    ;; Want to not count the current node, and stop after the last node\n    ;; in the subtree of it has been checked, which is the next node\n    ;; after the rightmost child.\n    (let [subtree-start-loc (-> hzip-loc zip/down)\n          has-children? (not= nil subtree-start-loc)]\n      ;; has-children? is needed to guard against zip/* receiving a nil arg in\n      ;; a selector.\n      (if has-children?\n        (let [subtree-end-loc (after-subtree hzip-loc)]\n          (if (select-next-loc selector subtree-start-loc\n                               zip/next\n                               #(= % subtree-end-loc))\n            hzip-loc))))))\n\n(defn ancestor\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the start of\n   a chain of descendant relationships specified by the selectors given\n   as arguments; intervening elements that do not satisfy a selector are\n   simply ignored and do not prevent a match.\n\n   Example: (ancestor (tag :div) (class :foo) (attr :disabled))\n     will select the div in both\n   <div><span class=\\\"foo\\\"><input disabled></input></span></div>\n     and\n   <div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n  [& selectors]\n  (apply compose-unary has-descendant selectors))\n\n(defn follow\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the end of\n   a chain of element sibling relationships specified by the selectors\n   given as arguments; intervening elements that do not satisfy a selector\n   are simply ignored and do not prevent a match.\n\n   Example: (follow (tag :div) (class :foo))\n     will select the span in both\n   <div>...</div><span class=\\\"foo\\\">...</span>\n     and\n   <div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n  [& selectors]\n  (apply ordered #(left-of-node-type % :element) (reverse selectors)))\n\n(defn precede\n  \"Takes any number of selectors as arguments and returns a selector that\n   returns true when the zip-loc given as the argument is at the beginning of\n   a chain of element sibling relationships specified by the selectors\n   given as arguments; intervening elements that do not satisfy a selector\n   are simply ignored and do not prevent a match.\n\n   Example: (precede (tag :div) (class :foo))\n     will select the div in both\n   <div>...</div><span class=\\\"foo\\\">...</span>\n     and\n   <div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n  [& selectors]\n  (apply ordered #(right-of-node-type % :element) selectors))\n"
  },
  {
    "path": "src/cljc/hickory/utils.cljc",
    "content": "(ns hickory.utils\n  \"Miscellaneous utilities used internally.\"\n  (:require [clojure.string :as string]\n            #?(:cljs [goog.string :as gstring])))\n\n;;\n;; Data\n;;\n\n(def void-element\n  \"Elements that don't have a meaningful <tag></tag> form.\"\n  #{:area :base :br :col :command :embed :hr :img :input :keygen :link :meta\n    :param :source :track :wbr})\n\n(def unescapable-content\n  \"Elements whose content should never have html-escape codes.\"\n  #{:script :style})\n\n;;\n;; String utils\n;;\n\n(defn clj-html-escape-without-quoin\n  \"Actually copy pasted from quoin: https://github.com/davidsantiago/quoin/blob/develop/src/quoin/text.clj\"\n  [^String s]\n  ;; This method is \"Java in Clojure\" for serious speedups.\n  #?(:clj (let [sb (StringBuilder.)\n                slength (long (count s))]\n            (loop [idx (long 0)]\n              (if (>= idx slength)\n                (.toString sb)\n                (let [c (char (.charAt s idx))]\n                  (case c\n                    \\& (.append sb \"&amp;\")\n                    \\< (.append sb \"&lt;\")\n                    \\> (.append sb \"&gt;\")\n                    \\\" (.append sb \"&quot;\")\n                    (.append sb c))\n                  (recur (inc idx))))))\n     ;; This shouldn't be called directly in cljs, but if it is, we use the same implementation as the html-escape function\n     :cljs (gstring/htmlEscape s)))\n\n(defn html-escape\n  [s]\n  #?(:clj  (clj-html-escape-without-quoin s)\n     :cljs (gstring/htmlEscape s)))\n\n(defn starts-with\n  [^String s ^String prefix]\n  #?(:clj  (.startsWith s prefix)\n     :cljs (goog.string.startsWith s prefix)))\n\n(defn lower-case-keyword\n  \"Converts its string argument into a lowercase keyword.\"\n  [s]\n  (-> s string/lower-case keyword))\n\n(defn render-doctype\n  \"Returns a string containing the HTML source for the doctype with given args.\n   The second and third arguments can be nil or empty strings.\"\n  [name publicid systemid]\n  (str \"<!DOCTYPE \" name\n       (when (not-empty publicid)\n         (str \" PUBLIC \\\"\" publicid \"\\\"\"))\n       (when (not-empty systemid)\n         (str \" \\\"\" systemid \"\\\"\"))\n       \">\"))\n"
  },
  {
    "path": "src/cljc/hickory/zip.cljc",
    "content": "(ns hickory.zip\n  (:require [clojure.zip :as zip]))\n\n;;\n;; Hickory\n;;\n\n(defn hickory-zip\n  \"Returns a zipper for html dom maps (as from as-hickory),\n  given a root element.\"\n  [root]\n  (zip/zipper (complement string?)\n              (comp seq :content)\n              (fn [node children]\n                (assoc node :content (and children (apply vector children))))\n              root))\n\n;;\n;; Hiccup\n;;\n\n;; Just to make things easier, we go ahead and do the work here to\n;; make hiccup zippers work on both normalized (all items have tag,\n;; attrs map, and any children) and unnormalized hiccup forms.\n\n(defn- children\n  \"Takes a hiccup node (normalized or not) and returns its children nodes.\"\n  [node]\n  (if (vector? node)\n    ;; It's a hiccup node vector.\n    (if (map? (second node)) ;; There is an attr map in second slot.\n      (seq (subvec node 2))  ;; So skip tag and attr vec.\n      (seq (subvec node 1))) ;; Otherwise, just skip tag.\n    ;; Otherwise, must have a been a node list\n    node))\n\n;; Note, it's not made clear at all in the docs for clojure.zip, but as far as\n;; I can tell, you are given a node potentially with existing children and\n;; the sequence of children that should totally replace the existing children.\n(defn- make\n  \"Takes a hiccup node (normalized or not) and a sequence of children nodes,\n   and returns a new node that has the the children argument as its children.\"\n  [node children]\n  ;; The node might be either a vector (hiccup form) or a seq (which is like a\n  ;; node-list).\n  (if (vector? node)\n    (if (map? (second node))                 ;; Again, check for normalized vec.\n      (into (subvec node 0 2) children)      ;; Attach children after tag&attrs.\n      (apply vector (first node) children))  ;; Otherwise, attach after tag.\n    children))   ;; We were given a list for node, so just return the new list.\n\n\n(defn hiccup-zip\n  \"Returns a zipper for Hiccup forms, given a root form.\"\n  [root]\n  (zip/zipper sequential?\n              children\n              make\n              root))\n"
  },
  {
    "path": "src/cljs/hickory/core.cljs",
    "content": "(ns hickory.core\n  (:require [hickory.utils :as utils]\n            [clojure.zip :as zip]\n            [goog.string :as gstring]\n            [goog.dom]\n            [goog.dom.NodeType]))\n\n;;\n;; Protocols\n;;\n\n(defprotocol HiccupRepresentable\n  \"Objects that can be represented as Hiccup nodes implement this protocol in\n   order to make the conversion.\"\n  (as-hiccup [this]\n    \"Converts the node given into a hiccup-format data structure. The\n     node must have an implementation of the HiccupRepresentable\n     protocol; nodes created by parse or parse-fragment already do.\"))\n\n(defprotocol HickoryRepresentable\n  \"Objects that can be represented as HTML DOM node maps, similar to\n   clojure.xml, implement this protocol to make the conversion.\n\n   Each DOM node will be a map or string (for Text/CDATASections). Nodes that\n   are maps have the appropriate subset of the keys\n\n     :type     - [:comment, :document, :document-type, :element]\n     :tag      - node's tag, check :type to see if applicable\n     :attrs    - node's attributes as a map, check :type to see if applicable\n     :content  - node's child nodes, in a vector, check :type to see if\n                 applicable\"\n  (as-hickory [this]\n    \"Converts the node given into a hickory-format data structure. The\n     node must have an implementation of the HickoryRepresentable protocol;\n     nodes created by parse or parse-fragment already do.\"))\n\n(defn node-type [type]\n  (case type\n    \"ELEMENT\" 1\n    \"ATTRIBUTE\" 2\n    \"TEXT\" 3\n    \"CDATA_SECTION\" 4\n    \"ENTITY_REFERENCE\" 5\n    \"ENTITY\" 6\n    \"PROCESSING_INSTRUCTION\" 7\n    \"COMMENT\" 8\n    \"DOCUMENT\" 9\n    \"DOCUMENT_TYPE\" 10\n    \"DOCUMENT_FRAGMENT\" 11\n    \"NOTATION\" 12))\n\n(def Attribute (node-type \"ATTRIBUTE\"))\n(def Comment (node-type \"COMMENT\"))\n(def Document (node-type \"DOCUMENT\"))\n(def DocumentType (node-type \"DOCUMENT_TYPE\"))\n(def Element (node-type \"ELEMENT\"))\n(def Text (node-type \"TEXT\"))\n\n(defn- as-seq [nodelist]\n  (if (seq? nodelist) nodelist (array-seq nodelist)))\n\n(defn format-doctype\n  [dt]\n  (let [name (.-name dt)\n        publicId (.-publicId dt)\n        systemId (.-systemId dt)]\n    (if (seq publicId)\n      (gstring/format \"<!DOCTYPE %s PUBLIC \\\"%s\\\" \\\"%s\\\">\" name publicId systemId)\n      (str \"<!DOCTYPE \" name \">\"))))\n\n(extend-protocol HiccupRepresentable\n  object\n  (as-hiccup [this] (condp = (.-nodeType this)\n                      Attribute [(utils/lower-case-keyword (.-name this))\n                                 (.-value this)]\n                      Comment (str \"<!--\" (.-data this) \"-->\")\n                      Document (map as-hiccup (as-seq (.-childNodes this)))\n                      DocumentType (format-doctype this)\n                      ;; There is an issue with the hiccup format, which is that it\n                      ;; can't quite cover all the pieces of HTML, so anything it\n                      ;; doesn't cover is thrown into a string containing the raw\n                      ;; HTML. This presents a problem because it is then never the case\n                      ;; that a string in a hiccup form should be html-escaped (except\n                      ;; in an attribute value) when rendering; it should already have\n                      ;; any escaping. Since the HTML parser quite properly un-escapes\n                      ;; HTML where it should, we have to go back and un-un-escape it\n                      ;; wherever text would have been un-escaped. We do this by\n                      ;; html-escaping the parsed contents of text nodes, and not\n                      ;; html-escaping comments, data-nodes, and the contents of\n                      ;; unescapable nodes.\n                      Element (let [tag (utils/lower-case-keyword (.-tagName this))]\n                                (into [] (concat [tag\n                                                  (into {} (map as-hiccup (as-seq (.-attributes this))))]\n                                                 (if (utils/unescapable-content tag)\n                                                   (map goog.dom.getRawTextContent (as-seq (.-childNodes this)))\n                                                   (map as-hiccup (as-seq (.-childNodes this)))))))\n                      Text (utils/html-escape (goog.dom.getRawTextContent this)))))\n\n(extend-protocol HickoryRepresentable\n  object\n  (as-hickory [this] (condp = (.-nodeType this)\n                       Attribute [(utils/lower-case-keyword (.-name this)) (.-value this)]\n                       Comment {:type :comment\n                                :content [(.-data this)]}\n                       Document {:type :document\n                                 :content (not-empty\n                                            (into [] (map as-hickory\n                                                          (as-seq (.-childNodes this)))))}\n                       DocumentType {:type :document-type\n                                     :attrs {:name (.-name this)\n                                             :publicid (.-publicId this)\n                                             :systemid (.-systemId this)}}\n                       Element {:type :element\n                                :attrs (not-empty (into {} (map as-hickory (as-seq (.-attributes this)))))\n                                :tag (utils/lower-case-keyword (.-tagName this))\n                                :content (not-empty\n                                           (into [] (map as-hickory\n                                                         (as-seq (.-childNodes this)))))}\n                       Text (goog.dom.getRawTextContent this))))\n\n(defn extract-doctype\n  [s]\n  ;;Starting HTML5 doctype definition can be uppercase\n  (when-let [doctype (second (or (re-find #\"<!DOCTYPE ([^>]*)>\" s)\n                                 (re-find #\"<!doctype ([^>]*)>\" s)))]\n    (re-find #\"([^\\s]*)(\\s+PUBLIC\\s+[\\\"]?([^\\\"]*)[\\\"]?\\s+[\\\"]?([^\\\"]*)[\\\"]?)?\" doctype)))\n\n(defn remove-el\n  [el]\n  (.removeChild (.-parentNode el) el))\n\n(defn parse-dom-with-domparser\n  [s]\n  (when (exists? js/DOMParser)\n    (.parseFromString (js/DOMParser.) s \"text/html\")))\n\n(defn parse-dom-with-write\n  \"Parse an HTML document (or fragment) as a DOM using document.implementation.createHTMLDocument and document.write.\"\n  [s]\n  ;;See http://www.w3.org/TR/domcore/#dom-domimplementation-createhtmldocument for more details.\n  (let [doc (.createHTMLDocument js/document.implementation \"\") ;;empty title for older implementation\n        doctype-el (.-doctype doc)]\n    (when-not (extract-doctype s);; Remove default doctype if parsed string does not define it.\n      (remove-el doctype-el))\n    (when-let [title-el (-> doc .-head .-firstChild)];; Remove default title if parsed string does not define it.\n      (when (empty? (.-text title-el))\n          (remove-el title-el)))\n    (.write doc s)\n    doc))\n\n(defn parse\n  \"Parse an entire HTML document into a DOM structure that can be\n   used as input to as-hiccup or as-hickory.\n\n```klipse\n  (-> (parse \\\"<a style=\\\\\\\"visibility:hidden\\\\\\\">foo</a><div style=\\\\\\\"color:green\\\\\\\"><p>Hello</p></div>\\\")\n    as-hiccup)\n```\n\n```klipse\n  (-> (parse \\\"<a style=\\\\\\\"visibility:hidden\\\\\\\">foo</a><div style=\\\\\\\"color:green\\\\\\\"><p>Hello</p></div>\\\")\n    as-hickory)\n```\n\n\n  \"\n  [s]\n  (or (parse-dom-with-domparser s) (parse-dom-with-write s)))\n\n(defn parse-fragment\n  \"Parse an HTML fragment (some group of tags that might be at home somewhere\n   in the tag hierarchy under <body>) into a list of DOM elements that can\n   each be passed as input to as-hiccup or as-hickory.\"\n  [s]\n  (as-seq (-> (parse s) .-body .-childNodes)))\n"
  },
  {
    "path": "test/cljc/hickory/test/convert.cljc",
    "content": "(ns hickory.test.convert\n  (:require\n   [clojure.test :refer [deftest is]]\n   [hickory.convert :refer [hiccup-fragment-to-hickory hiccup-to-hickory\n                            hickory-to-hiccup]]\n   [hickory.core :refer [as-hiccup as-hickory parse parse-fragment]]))\n\n(deftest hiccup-to-hickory-test\n  (is (= (as-hickory (parse \"<i>Hi.</i>\"))\n         (hiccup-to-hickory (as-hiccup (parse \"<i>Hi.</i>\")))))\n  (is (= (as-hickory (parse \"<i>Outer<b class=\\\"foo\\\">Inner.</b></i>\"))\n         (hiccup-to-hickory (as-hiccup (parse \"<i>Outer<b class=\\\"foo\\\">Inner.</b></i>\")))))\n  (is (= (as-hickory (parse \"<a href='http://localhost/?a=1&amp;b=2'>Hi</a>\"))\n         (hiccup-to-hickory (as-hiccup (parse \"<a href='http://localhost/?a=1&amp;b=2'>Hi</a>\")))))\n  (is (= (as-hickory (parse \"<script>alert();</script>\"))\n         (hiccup-to-hickory (as-hiccup (parse \"<script>alert();</script>\"))))))\n\n(deftest hiccup-fragment-to-hickory-test\n  (is (= (map as-hickory (parse-fragment \"<img src=\\\"a.jpg\\\">\"))\n         (hiccup-fragment-to-hickory (map as-hiccup (parse-fragment \"<img src=\\\"a.jpg\\\">\")))))\n  (let [src \"<a href=\\\"/a.txt\\\"><img src=\\\"a.jpg\\\"></a><b>It's an a.</b>\"]\n    (is (= (map as-hickory (parse-fragment src))\n           (hiccup-fragment-to-hickory (map as-hiccup (parse-fragment src)))))))\n\n\n(deftest hickory-to-hiccup-test\n  (is (= (as-hiccup (parse \"<i>Hi.</i>\"))\n         (hickory-to-hiccup (as-hickory (parse \"<i>Hi.</i>\")))))\n  (is (= (as-hiccup (parse \"<i>Outer<b class=\\\"foo\\\">Inner.</b></i>\"))\n         (hickory-to-hiccup (as-hickory (parse \"<i>Outer<b class=\\\"foo\\\">Inner.</b></i>\")))))\n  (is (= (as-hiccup (parse \"<a href='http://localhost/?a=1&amp;b=2'>Hi</a>\"))\n         (hickory-to-hiccup (as-hickory (parse \"<a href='http://localhost/?a=1&amp;b=2'>Hi</a>\")))))\n  (is (= (as-hiccup (parse \"<script>alert();</script>\"))\n         (hickory-to-hiccup (as-hickory (parse \"<script>alert();</script>\")))))\n  ;; Fragments\n  (is (= (map as-hiccup (parse-fragment \"<img src=\\\"a.jpg\\\">\"))\n         (map hickory-to-hiccup (map as-hickory (parse-fragment \"<img src=\\\"a.jpg\\\">\")))))\n  (let [src \"<a href=\\\"/a.txt\\\"><img src=\\\"a.jpg\\\"></a><b>It's an a.</b>\"]\n    (is (= (map as-hiccup (parse-fragment src))\n           (map hickory-to-hiccup (map as-hickory (parse-fragment src)))))))\n\n"
  },
  {
    "path": "test/cljc/hickory/test/core.cljc",
    "content": "(ns hickory.test.core\n  (:require [hickory.core :refer [as-hickory as-hiccup parse parse-fragment]]\n            [clojure.test :refer [deftest is]]))\n\n;; This document tests: doctypes, white space text nodes, attributes,\n;; and cdata nodes.\n(deftest basic-documents\n  (is (= [\"<!DOCTYPE html>\"\n          [:html {}\n           [:head {}]\n           [:body {}\n            [:a {:href \"foo\"} \"foo\"] \" \"\n            [:a {:id \"so\", :href \"bar\"} \"bar\"]\n            [:script {:src \"blah.js\"} \"alert(\\\"hi\\\");\"]]]]\n         (as-hiccup (parse \"<!DOCTYPE html><a href=\\\"foo\\\">foo</a> <a id=\\\"so\\\" href=\\\"bar\\\">bar</a><script src=\\\"blah.js\\\">alert(\\\"hi\\\");</script>\"))))\n\n  (is (= {:type :document,\n          :content [{:type :document-type,\n                     :attrs {:name \"html\", :publicid \"\", :systemid \"\"\n                             #?@(:clj [:#doctype \"html\"])}}\n                    {:type :element,\n                     :attrs nil,\n                     :tag :html,\n                     :content [{:type :element,\n                                :attrs nil,\n                                :tag :head,\n                                :content nil}\n                               {:type :element,\n                                :attrs nil,\n                                :tag :body,\n                                :content [{:type :element,\n                                           :attrs {:href \"foo\"},\n                                           :tag :a,\n                                           :content [\"foo\"]}\n                                          \" \"\n                                          {:type :element,\n                                           :attrs {:id \"so\", :href \"bar\"},\n                                           :tag :a,\n                                           :content [\"bar\"]}\n                                          {:type :element,\n                                           :attrs {:src \"blah.js\"},\n                                           :tag :script,\n                                           :content [\"alert(\\\"hi\\\");\"]}]}]}]}\n         (as-hickory (parse \"<!DOCTYPE html><a href=\\\"foo\\\">foo</a> <a id=\\\"so\\\" href=\\\"bar\\\">bar</a><script src=\\\"blah.js\\\">alert(\\\"hi\\\");</script>\")))))\n\n;; This document tests: doctypes, comments, white space text nodes, attributes,\n;; and cdata nodes.\n(deftest basic-documents2\n  (is (= [\"<!DOCTYPE html>\"\n           [:html {}\n            [:head {}]\n            [:body {}\n             \"<!--comment-->\"\n             [:a {:href \"foo\"} \"foo\"] \" \"\n             [:a {:id \"so\", :href \"bar\"} \"bar\"]\n             [:script {:src \"blah.js\"} \"alert(\\\"hi\\\");\"]]]]\n          (as-hiccup (parse \"<!DOCTYPE html><body><!--comment--><a href=\\\"foo\\\">foo</a> <a id=\\\"so\\\" href=\\\"bar\\\">bar</a><script src=\\\"blah.js\\\">alert(\\\"hi\\\");</script></body>\"))))\n\n  (is (= {:type :document,\n          :content [{:type :document-type,\n                     :attrs {:name \"html\", :publicid \"\", :systemid \"\"\n                             #?@(:clj [:#doctype \"html\"])}}\n                    {:type :element,\n                     :attrs nil,\n                     :tag :html,\n                     :content [{:type :element,\n                                :attrs nil,\n                                :tag :head,\n                                :content nil}\n                               {:type :element,\n                                :attrs nil,\n                                :tag :body,\n                                :content [{:type :comment\n                                           :content [\"comment\"]}\n                                          {:type :element,\n                                           :attrs {:href \"foo\"},\n                                           :tag :a,\n                                           :content [\"foo\"]}\n                                          \" \"\n                                          {:type :element,\n                                           :attrs {:id \"so\", :href \"bar\"},\n                                           :tag :a,\n                                           :content [\"bar\"]}\n                                          {:type :element,\n                                           :attrs {:src \"blah.js\"},\n                                           :tag :script,\n                                           :content [\"alert(\\\"hi\\\");\"]}]}]}]}\n         (as-hickory (parse \"<!DOCTYPE html><body><!--comment--><a href=\\\"foo\\\">foo</a> <a id=\\\"so\\\" href=\\\"bar\\\">bar</a><script src=\\\"blah.js\\\">alert(\\\"hi\\\");</script></body>\")))))\n\n;; Want to test a document fragment that has multiple nodes with no parent,\n;; as well as a text node between nodes.\n(deftest basic-document-fragment\n  (is (= [[:a {:href \"foo\"} \"foo\"] \" \"\n          [:a {:href \"bar\"} \"bar\"]]\n         (map as-hiccup\n              (parse-fragment \"<a href=\\\"foo\\\">foo</a> <a href=\\\"bar\\\">bar</a>\"))))\n  (is (= [{:type :element,\n           :attrs {:href \"foo\"},\n           :tag :a,\n           :content [\"foo\"]}\n          \" \"\n          {:type :element,\n           :attrs {:href \"bar\"},\n           :tag :a,\n           :content [\"bar\"]}]\n         (map as-hickory\n              (parse-fragment \"<a href=\\\"foo\\\">foo</a> <a href=\\\"bar\\\">bar</a>\")))))\n\n(deftest unencoded-text-nodes\n  ;; Hiccup versions - Note that hiccup representation does not html-escape any\n  ;; strings that aren't attribute values, so the hiccup representation will\n  ;; have the string contents html-escaped.\n  (is (= [[:html {} [:head {}] [:body {} [:p {} \"ABC&amp;\\n\\nDEF.\"]]]]\n         (as-hiccup (parse \"<p>ABC&amp;\\n\\nDEF.</p>\"))))\n  ;; <pre> tag preserves whitespace.\n  (is (= [[:html {} [:head {}] [:body {} [:pre {} \"ABC&amp;\\n\\nDEF.\"]]]]\n         (as-hiccup (parse \"<pre>ABC&amp;\\n\\nDEF.</pre>\"))))\n  ;; Hickory versions - Note that the representation is different, and Hickory\n  ;; format does not keep HTML escaped in its representation, as it can\n  ;; figure out what to escape at render time.\n  (is (= \"ABC&\\n\\nDEF.\"\n         (get-in (as-hickory (parse \"<p>ABC&amp;\\n\\nDEF.</p>\"))\n                 [:content 0 :content 1 :content 0 :content 0])))\n  ;; <pre> tag preserves whitespace.\n  (is (= \"ABC&\\n\\nDEF.\"\n         (get-in (as-hickory (parse \"<pre>ABC&amp;\\n\\nDEF.</pre>\"))\n                 [:content 0 :content 1 :content 0 :content 0]))))\n\n;; Issue #50: Tests that the parser does not throw a StackOverflowError when\n;; parsing a document with deeply nested HTML tags.\n;; I don't have time for this\n#_(deftest deeply-nested-tags\n  (let [jsoup (parse (apply str (repeat 2048 \"<font>abc\")))\n        r1 (get-in (vec (as-hiccup jsoup))\n                   (concat [0 3 2] (repeat 2047 3)))\n        r2 (get-in (as-hickory jsoup)\n                   (apply concat\n                          [:content 0 :content 1 :content 0]\n                          (repeat 2047 [:content 1])))]\n    (println \"R1\" r1)\n    (println \"R2\" r2)\n    (is (= [:font {} \"abc\"]\n           r1))\n    (is (= {:type :element\n            :attrs nil\n            :tag :font\n            :content [\"abc\"]}\n           r2))))\n\n#?(:cljs\n   (deftest node-type-test\n     (is (= 3 (hickory.core/node-type \"TEXT\")))))\n"
  },
  {
    "path": "test/cljc/hickory/test/hiccup_utils.cljc",
    "content": "(ns hickory.test.hiccup-utils\n  (:require [hickory.hiccup-utils :refer [class-names id normalize-form\n                                          tag-name tag-well-formed?]]\n            [clojure.test :refer [deftest is]]))\n\n#?(:clj\n   (deftest first-idx-test\n     (let [first-idx #'hickory.hiccup-utils/first-idx]\n       (is (= -1 (first-idx -1 -1)))\n       (is (= 2 (first-idx -1 2)))\n       (is (= 5 (first-idx 5 -1)))\n       (is (= 3 (first-idx 5 3)))\n       (is (= 3 (first-idx 3 5))))))\n\n(deftest tag-well-formed?-test\n  (is (= true (tag-well-formed? :a)))\n  (is (= true (tag-well-formed? :a#id)))\n  (is (= true (tag-well-formed? :a#id.class)))\n  (is (= true (tag-well-formed? :a.class.class2)))\n  (is (= false (tag-well-formed? \"\")))\n  (is (= false (tag-well-formed? \".class\")))\n  (is (= false (tag-well-formed? \"a#\")))\n  (is (= false (tag-well-formed? \"a#foo.\")))\n  (is (= false (tag-well-formed? \"a.\")))\n  (is (= false (tag-well-formed? \"a.foo.\")))\n  (is (= false (tag-well-formed? \"#id.class\")))\n  (is (= false (tag-well-formed? :a.class#id)))\n  (is (= false (tag-well-formed? :a#id#id2))))\n\n(deftest tag-name-test\n  (is (= \"a\" (tag-name \"a\")))\n  (is (= \"a\" (tag-name 'a)))\n  (is (= \"a\" (tag-name :a)))\n  (is (= \"b\" (tag-name :b.class)))\n  (is (= \"b\" (tag-name :b#id)))\n  (is (= \"b\" (tag-name :b.class#id)))\n  (is (= \"b\" (tag-name :b#id.class))))\n\n(deftest class-names-test\n  (is (= [] (class-names :a)))\n  (is (= [] (class-names :a#foo)))\n  (is (= [\"foo\"] (class-names \"a.foo\")))\n  (is (= [\"bar\"] (class-names :a#foo.bar)))\n  (is (= [\"foo\" \"bar\"] (class-names :a.foo.bar))))\n\n(deftest id-test\n  (is (= nil (id :a)))\n  (is (= nil (id 'a)))\n  (is (= \"foo\" (id :a#foo)))\n  (is (= \"foo\" (id :a#foo.bar))))\n\n#?(:clj\n   (deftest expand-content-seqs-test\n     (let [expand-content-seqs #'hickory.hiccup-utils/expand-content-seqs]\n       (is (= [1 2 3] (expand-content-seqs [1 2 3])))\n       (is (= [1 2 [3]] (expand-content-seqs [1 '(2 [3])])))\n       ;; Example from docstring.\n       (is (= [1 2 3 2 4 6 [5]]\n              (expand-content-seqs [1 '(2 3) (for [x [1 2 3]] (* x 2)) [5]]))))))\n\n#?(:clj\n   (deftest normalize-element-test\n     (let [normalize-element #'hickory.hiccup-utils/normalize-element]\n       (is (= [:a {:id nil :class nil} \"Hi\"] (normalize-element [:a \"Hi\"])))\n       (is (= [:a {:id \"foo\" :class nil} \"Hi\"]\n              (normalize-element [:A#foo \"Hi\"])))\n       (is (= [:a {:id nil :class \"foo\"} \"Hi\"]\n              (normalize-element [:a.foo \"Hi\"])))\n       (is (= [:a {:id \"foo\" :class \"bar\"} \"Hi\" \"There\"]\n              (normalize-element [:a#foo.bar \"Hi\" \"There\"])))\n       (is (= [:a {:id \"foo\" :class \"bar\"} \"Hi\"]\n              (normalize-element [:a.bar {:id \"foo\"} \"Hi\"])))\n       (is (= [:a {:id \"foo\" :class \"bar\"}]\n              (normalize-element [:A#bip {:id \"foo\" :class \"bar\"}])))\n       (is (= [:a {:id \"foo\" :class \"bar\"}]\n              (normalize-element [:a#bip.baz {:id \"foo\" :class \"bar\"}])))\n       (is (= [:a {:id nil :class \"foo bar\"}]\n              (normalize-element [:a.foo.bar]))))))\n\n(deftest normalize-form-test\n  (is (= [:a {:id nil :class nil}] (normalize-form [:A])))\n  (is (= [:a {:id nil :class nil :href \"localhost\"}]\n         (normalize-form [:a {:href \"localhost\"}])))\n  (is (= [:a {:id nil :class nil}\n          [:b {:id nil :class nil} \"foo\"]\n          [:i {:id nil :class nil} \"bar\"]]\n         (normalize-form [:a [:b \"foo\"] [:i \"bar\"]])))\n  (is (= [:a {:id nil :class nil}\n          [:b {:id nil :class nil} \"foo\"]\n          [:i {:id nil :class nil} \"bar\"]]\n         (normalize-form [:a '([:b \"foo\"] [:i \"bar\"])])))\n  (is (= [:a {:id nil :class nil}\n          [:b {:id nil :class nil} \"foo\" [:i {:id nil :class nil} \"bar\"]]]\n         (normalize-form [:a [:b \"foo\" [:i \"bar\"]]]))))\n"
  },
  {
    "path": "test/cljc/hickory/test/render.cljc",
    "content": "(ns hickory.test.render\n  (:require\n   [clojure.test :refer [deftest is]]\n   [hickory.core :refer [as-hiccup as-hickory parse parse-fragment]]\n   [hickory.render :refer [hiccup-to-html hickory-to-html]]))\n;;\n;; Hickory to HTML\n;;\n\n(deftest hickory-to-html-test\n  (is (= \"<!DOCTYPE html><html><head></head><body><p><!--hi--><a href=\\\"foo\\\" id=\\\"bar\\\">hi</a></p></body></html>\"\n         (hickory-to-html (as-hickory (parse \"<!DOCTYPE html><P><!--hi--><a href=foo id=\\\"bar\\\">hi\")))))\n  ;; Make sure void elements don't have closing tags.\n  (is (= \"<html><head></head><body>Hi<br>There</body></html>\"\n         (hickory-to-html (as-hickory (parse \"<html><head></head><body>Hi<br>There</body></html>\")))))\n  ;; Make sure text is properly escaped.\n  (is (= \"<code>&lt;html&gt;</code>\"\n         (hickory-to-html (as-hickory (first (parse-fragment \"<code>&lt;html&gt;</code>\"))))))\n  ;; Make sure the contents of script/style tags do not get html escaped.\n  (is (= \"<script>Test<!--Test&Test-->Test</script>\"\n         (hickory-to-html (as-hickory\n                           (first (parse-fragment \"<body><script>Test<!--Test&Test-->Test</script></body>\"))))))\n  ;; Make sure attribute contents are html-escaped.\n  (is (= \"<img fake-attr=\\\"abc&quot;def\\\">\"\n         (hickory-to-html (as-hickory (first (parse-fragment \"<img fake-attr=\\\"abc&quot;def\\\">\")))))))\n\n(deftest hickory-doctypes-test\n  (is (= \"<!DOCTYPE html>\"\n         (hickory-to-html {:type :document-type\n                           :attrs {:name \"html\",\n                                   :publicid nil\n                                   :systemid nil}})))\n  (is (= \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD HTML 4.01//EN\\\" \\\"http://www.w3.org/TR/html4/strict.dtd\\\">\"\n         (hickory-to-html {:type :document-type\n                           :attrs {:name \"html\",\n                                   :publicid \"-//W3C//DTD HTML 4.01//EN\",\n                                   :systemid \"http://www.w3.org/TR/html4/strict.dtd\"}})))\n  (is (= \"<!DOCTYPE html><html><head></head><body></body></html>\"\n         (hickory-to-html (as-hickory (parse \"<!DOCTYPE html><html><head></head><body></body></html>\")))))\n  ;; Apparently Chrome will parse this doctype as plain html5, so we can't\n  ;; do a roundtrip test in cljs.\n  #?(:clj (is (= \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD HTML 4.01//EN\\\" \\\"http://www.w3.org/TR/html4/strict.dtd\\\"><html><head></head><body></body></html>\"\n                 (hickory-to-html (as-hickory (parse \"<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD HTML 4.01//EN\\\" \\\"http://www.w3.org/TR/html4/strict.dtd\\\"><html><head></head><body></body</html>\")))))))\n\n\n(deftest error-handling-test\n  (let [data {:type :foo :tag :a :attrs {:foo \"bar\"}}]\n    (is (thrown-with-msg? #?(:clj Exception :cljs :default) #\"^Not a valid node: nil\"\n                          (hickory-to-html nil)))\n    (is (thrown-with-msg? #?(:clj Exception :cljs :default) #?(:clj  #\"^Not a valid node: \\{:type :foo, :tag :a, :attrs \\{:foo \\\"bar\\\"\\}\\}\"\n                                                               :cljs #\"^Not a valid node: \\{:type :foo, :tag :a\\, :attrs \\{:foo \\\"bar\\\"\\}}\")\n                          (hickory-to-html data)))\n    (is (= data\n           (try (hickory-to-html data)\n                (catch #?(:clj Exception :cljs js/Error) e (:dom (ex-data e))))))))\n\n;;\n;; Hiccup to HTML\n;;\n\n(deftest hiccup-to-html-test\n  (is (= \"<!DOCTYPE html><html><head></head><body><p><!--hi--><a href=\\\"foo\\\" id=\\\"bar\\\">hi</a></p></body></html>\"\n                  (hiccup-to-html (as-hiccup (parse \"<!DOCTYPE html><P><!--hi--><a href=foo id=\\\"bar\\\">hi\")))))\n  ;; Make sure void elements don't have closing tags.\n  (is (= \"<html><head></head><body>Hi<br>There</body></html>\"\n         (hiccup-to-html (as-hiccup (parse \"<html><head></head><body>Hi<br>There</body></html>\")))))\n  ;; Make sure text is properly escaped.\n  (is (= \"<code>&lt;html&gt;</code>\"\n         (hiccup-to-html [(as-hiccup (first (parse-fragment \"<code>&lt;html&gt;</code>\")))])))\n  ;; Make sure the contents of script/style tags do not get html escaped.\n  (is (= \"<script>Test<!--Test&Test-->Test</script>\"\n         (hiccup-to-html [(as-hiccup\n                            (first (parse-fragment \"<body><script>Test<!--Test&Test-->Test</script></body>\")))])))\n  ;; Make sure attribute contents are html-escaped.\n  (is (= \"<img fake-attr=\\\"abc&quot;def\\\">\"\n         (hiccup-to-html [(as-hiccup (first (parse-fragment \"<img fake-attr=\\\"abc&quot;def\\\">\")))]))))\n\n(deftest hiccup-doctypes-test\n  (is (= \"<!DOCTYPE html><html><head></head><body></body></html>\"\n           (hiccup-to-html (as-hiccup (parse \"<!DOCTYPE html><html><head></head><body></body></html>\")))))\n  #?(:clj (is (= \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD HTML 4.01//EN\\\" \\\"http://www.w3.org/TR/html4/strict.dtd\\\"><html><head></head><body></body></html>\"\n                 (hiccup-to-html (as-hiccup (parse \"<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD HTML 4.01//EN\\\" \\\"http://www.w3.org/TR/html4/strict.dtd\\\"><html><head></head><body></body</html>\")))))))\n"
  },
  {
    "path": "test/cljc/hickory/test/select.cljc",
    "content": "(ns hickory.test.select\n  (:require [hickory.core :as hickory]\n            [hickory.select :as select]\n            [hickory.utils :as utils]\n            [hickory.zip :as hzip]\n            [clojure.zip :as zip]\n            [clojure.test :refer [deftest is testing]]))\n\n(def html1\n  \"<!DOCTYPE html>\n    <!-- Comment 1 -->\n    <html>\n    <head></head>\n    <body>\n        <h1>Heading</h1>\n        <p>Paragraph</p>\n        <a href=\\\"http://example.com\\\">Link</a>\n        <div class=\\\"aclass bclass cool\\\">\n            <span disabled anotherattr=\\\"\\\" thirdthing=\\\"44\\\" id=\\\"attrspan\\\" Capitalized=\\\"UPPERCASED\\\">\n                <div class=\\\"subdiv cool\\\" id=\\\"deepestdiv\\\">Div</div>\n            </span>\n            <!-- Comment 2 -->\n            <span id=\\\"anid\\\" class=\\\"line-feed-ahead cool\\\">Span</span>\n        </div>\n    </body>\n    </html>\")\n\n(def html2\n  \"<!DOCTYPE html>\n    <html>\n    <head></head>\n    <body>\n        <p>Paragraph 1</p>\n        <p>Paragraph 2</p>\n        <p>Paragraph 3</p>\n        <p>Paragraph 4</p>\n        <p>Paragraph 5</p>\n        <p>Paragraph 6</p>\n        <p>Paragraph 7</p>\n        <p>Paragraph 8</p>\n    </body>\n    </html>\")\n\n(deftest select-next-loc-test\n  (testing \"The select-next-loc function.\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))\n          find-comment-fn (fn [zip-loc]\n                            (= (:type (zip/node zip-loc))\n                               :comment))]\n      (let [selection (select/select-next-loc find-comment-fn\n                                              (hzip/hickory-zip htree))]\n        (is (and (= :comment\n                    (-> selection zip/node :type))\n                 (re-find #\"Comment 1\" (-> (zip/node selection)\n                                           :content first))))\n        (let [second-selection (select/select-next-loc find-comment-fn\n                                                       (zip/next selection))]\n          (is (and (= :comment\n                      (-> second-selection zip/node :type))\n                   (re-find #\"Comment 2\" (-> (zip/node second-selection)\n                                             :content first))))\n          (is (nil? (select/select-next-loc find-comment-fn\n                                            (zip/next second-selection)))))))))\n\n(deftest select-test\n  (testing \"The select function.\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (fn [zip-loc]\n                                       (= (:type (zip/node zip-loc))\n                                          :document-type))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :document-type\n                    (-> selection first :type)))))\n      (let [selection (select/select (fn [zip-loc]\n                                       (= (:type (zip/node zip-loc))\n                                          :comment))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :comment (:type %))\n                                    selection))))))))\n\n;;\n;; Selector tests\n;;\n\n(deftest node-type-test\n  (testing \"node-type selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/node-type :document-type)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :document-type (-> selection first :type)))))\n      (let [selection (select/select (select/node-type :comment)\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :comment (:type %))\n                                    selection))))))))\n\n(deftest tag-test\n  (testing \"tag selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/tag \"h1\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :h1 (-> selection first :tag)))))\n      ;; Case-insensitivity test\n      (let [selection (select/select (select/tag \"H1\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :h1 (-> selection first :tag)))))\n      ;; Non-string argument test\n      (let [selection (select/select (select/tag :h1)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :h1 (-> selection first :tag))))))))\n\n(deftest attr-test\n  (testing \"attr selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/attr :disabled)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"attrspan\" (-> selection first :attrs :id)))))\n      (let [selection (select/select (select/attr \"anotherattr\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"attrspan\" (-> selection first :attrs :id)))))\n      (let [selection (select/select (select/attr :thirdthing #(= \"44\" %))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"attrspan\" (-> selection first :attrs :id)))))\n      ;; Case-insensitivity of names and non-equality predicate test\n      (let [selection (select/select (select/attr \"CAPITALIZED\"\n                                                  #(utils/starts-with % \"UPPER\"))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"attrspan\" (-> selection first :attrs :id)))))\n      ;; Graceful failure to find anything\n      (let [selection (select/select (select/attr \"notpresent\"\n                                                  #(utils/starts-with % \"never\"))\n                                     htree)]\n        (is (= 0 (count selection)))))))\n\n(deftest id-test\n  (testing \"id selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/id \"deepestdiv\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"deepestdiv\"\n                          (-> selection first :attrs :id)))))\n      (let [selection (select/select (select/id \"anid\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"anid\"\n                          (-> selection first :attrs :id)))))\n      ;; Case-insensitivity test\n      (let [selection (select/select (select/id \"ANID\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"anid\"\n                          (-> selection first :attrs :id)))))\n      ;; Non-string argument test\n      (let [selection (select/select (select/id :anid)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"anid\"\n                          (-> selection first :attrs :id))))))))\n\n(deftest class-test\n  (testing \"class selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/class \"aclass\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"aclass\"\n                          (-> selection first :attrs :class)))))\n      (let [selection (select/select (select/class \"cool\")\n                                     htree)]\n        (is (and (= 3 (count selection))\n                 (every? #(not (nil? %))\n                         (map #(re-find #\"cool\"\n                                        (-> % :attrs :class))\n                              selection)))))\n      ;; Case-insensitivity test\n      (let [selection (select/select (select/class \"Aclass\")\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"aclass\"\n                          (-> selection first :attrs :class)))))\n      ;; Non-string argument test\n      (let [selection (select/select (select/class :aclass)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"aclass\"\n                          (-> selection first :attrs :class)))))\n      ;; class followed by line feed\n      (let [selection (select/select (select/class :line-feed-ahead)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"line-feed-ahead\"\n                          (-> selection first :attrs :class))))))))\n\n(deftest any-test\n  (testing \"any selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select select/any\n                                     htree)]\n        (is (= 10 (count selection)))))))\n\n(deftest element-child-test\n  (testing \"element-child selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select select/element-child htree)]\n        (is (= 9 (count selection)))))))\n\n(deftest root-test\n  (testing \"root selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select select/root\n                                     htree)]\n        (is (= :html (-> selection first :tag)))))))\n\n(deftest find-in-text-test\n  (testing \"find-in-text selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/find-in-text #\"Heading\") htree)]\n        (is (and (= 1 (count selection))\n                 (= :h1 (-> selection first :tag)))))\n      (let [selection (select/select (select/find-in-text #\"Div\") htree)]\n        (is (and (= 1 (count selection))\n                 (= :div (-> selection first :tag)))))\n      (let [selection-locs (select/select-locs\n                            (select/child (select/tag :body)\n                                          (select/find-in-text #\"Paragraph\"))\n                            htree)\n            selection (mapv zip/node selection-locs)]\n        (is (and (= 1 (count selection))\n                 (= :p (-> selection first :tag))\n                 (= :body (-> selection-locs first zip/up zip/node :tag))))))\n    (let [htree (hickory/as-hickory (hickory/parse html2))]\n      (let [selection (select/select (select/find-in-text #\"Paragraph\") htree)]\n        (is (and (= 8 (count selection))\n                 (every? #(= :p %) (map :tag selection))))))))\n\n(deftest n-moves-until-test\n  (testing \"n-moves-until selector\"\n    ;; This function is actually pretty well exercised by nth-child, etc.\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/n-moves-until 0 6\n                                                                       zip/up\n                                                                       nil?))\n                                     htree)]\n        (is (= \"deepestdiv\" (-> selection first :attrs :id)))))))\n\n(deftest nth-of-type-test\n  (testing \"nth-of-type selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/nth-of-type 1 :body)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :body (:tag (first selection)))))))))\n\n(deftest nth-last-of-type-test\n  (testing \"nth-last-of-type selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/nth-last-of-type 1 :span)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"anid\" (-> selection first :attrs :id))))))))\n\n(deftest nth-child-test\n  (testing \"nth-child selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/nth-child 0 1))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"deepestdiv\" (-> selection first :attrs :id)))))\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/nth-child 1 1))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %))\n                                    selection)))))\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/nth-child :odd))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"deepestdiv\" (-> selection first :attrs :id)))))\n      (let [selection (select/select (select/and (select/node-type :element)\n                                                 (select/nth-child :even))\n                                     htree)]\n        (is (and (= 4 (count selection))\n                 (= :element (-> selection first :type))))))\n    (let [htree (hickory/as-hickory (hickory/parse html2))]\n      (let [selection (select/select (select/and (select/node-type :element)\n                                                 (select/nth-child :even))\n                                     htree)]\n        (is (and (= 5 (count selection))\n                 (every? true? (map #(contains? #{:body :p} (:tag %))\n                                    selection)))))\n      (let [selection (select/select (select/nth-child 3 0)\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :p (:tag %))\n                                    selection)))))\n      (let [selection (select/select (select/child (select/tag :body)\n                                                   (select/nth-child 3 1))\n                                     htree)]\n        (is (and (= 3 (count selection))\n                 (every? true? (map #(= :p (:tag %))\n                                    selection))))))))\n\n(deftest nth-last-child-test\n  (testing \"nth-last-child selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/nth-last-child 0 1))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %)) selection)))))\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/nth-last-child 1 1))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %))\n                                    selection)))))\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/nth-last-child :odd))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %)) selection)))))\n      (let [selection (select/select (select/and (select/node-type :element)\n                                                 (select/nth-last-child :even))\n                                     htree)]\n        (is (and (= 4 (count selection))\n                 (= :element (-> selection first :type))))))\n    (let [htree (hickory/as-hickory (hickory/parse html2))]\n      (let [selection (select/select (select/and (select/node-type :element)\n                                                 (select/nth-last-child :even))\n                                     htree)]\n        (is (and (= 5 (count selection))\n                 (every? true? (map #(contains? #{:head :p} (:tag %))\n                                    selection)))))\n      (let [selection (select/select (select/nth-last-child 3 0)\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :p (:tag %))\n                                    selection)))))\n      (let [selection (select/select (select/child (select/tag :body)\n                                                   (select/nth-last-child 3 1))\n                                     htree)]\n        (is (and (= 3 (count selection))\n                 (every? true? (map #(= :p (:tag %))\n                                    selection))))))))\n\n(deftest first-child-test\n  (testing \"first-child selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/child (select/tag :div)\n                                                   select/first-child)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"attrspan\" (-> selection first :attrs :id))))))))\n\n(deftest last-child-test\n  (testing \"last-child selector\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/child (select/tag :div)\n                                                   select/last-child)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"anid\" (-> selection first :attrs :id))))))))\n\n;;\n;; Selector Combinators\n;;\n\n(deftest and-test\n  (testing \"and selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/and (select/tag :div))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %)) selection)))))\n      (let [selection (select/select (select/and (select/tag :div)\n                                                 (select/class \"bclass\"))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (re-find #\"bclass\"\n                          (-> selection first :attrs :class)))))\n      (let [selection (select/select (select/and (select/class \"cool\")\n                                                 (select/tag :span))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= \"anid\" (-> selection first :attrs :id)))))\n\n      (let [selection (select/select (select/and (select/class \"cool\")\n                                                 (select/tag :span)\n                                                 (select/id :attrspan))\n                                     htree)]\n        (is (= [] selection))))))\n\n(deftest or-test\n  (testing \"or selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/or (select/tag :a)\n                                                (select/class \"notpresent\")\n                                                (select/id :nothere))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (every? true? (map #(= :a (:tag %)) selection)))))\n      (let [selection (select/select (select/or (select/tag :div))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %)) selection)))))\n      (let [selection (select/select (select/or (select/id \"deepestdiv\")\n                                                (select/class \"bclass\"))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :div (:tag %)) selection))))))))\n\n(deftest not-test\n  (testing \"not selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/and (select/node-type :element)\n                                                 (select/not (select/class :cool)))\n                                     htree)]\n        (is (and (= 7 (count selection))\n                 (every? true? (map #(and (= :element (-> % :type))\n                                          (or (not (-> % :attrs :class))\n                                              (not (re-find #\"cool\"\n                                                            (-> % :attrs :class)))))\n                                    selection)))))\n      (let [selection (select/select (select/el-not (select/class :cool))\n                                     htree)]\n        (is (and (= 7 (count selection))\n                 (every? true? (map #(and (= :element (-> % :type))\n                                          (or (not (-> % :attrs :class))\n                                              (not (re-find #\"cool\"\n                                                            (-> % :attrs :class)))))\n                                    selection))))))))\n\n(deftest ordered-adjacent-test\n  (testing \"ordered-adjacent selector combinator\"\n    ;; This is pretty well tested by the tests for child and others.\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      ;; Select body tag that is just after a head tag.\n      (let [selection (select/select\n                       (select/ordered-adjacent\n                        #(select/left-of-node-type % :element)\n                        (select/tag :body)\n                        (select/tag :head))\n                       htree)]\n        (is (and (= 1 (count selection))\n                 (= :body (-> selection first :tag))))))))\n\n(deftest child-test\n  (testing \"child selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/child (select/el-not select/any))\n                                     htree)]\n        (is (= [] selection)))\n      (let [selection (select/select (select/child (select/tag :html)\n                                                   (select/tag :div)\n                                                   (select/tag :span))\n                                     htree)]\n        (is (= [] selection)))\n      (let [selection (select/select (select/child (select/tag :body)\n                                                   (select/tag :div)\n                                                   (select/tag :span))\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(= :span (:tag %)) selection)))))\n      (let [selection (select/select (select/child (select/tag :div)\n                                                   select/any)\n                                     htree)]\n        (is (and (= 2 (count selection))\n                 (every? true? (map #(or (= :span (-> % :tag))\n                                         (= :div (-> % :tag)))\n                                    selection))))))\n    ;; Check examples from the doc string.\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><input disabled></input></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/child (select/tag :div)\n                                                   (select/class :foo)\n                                                   (select/attr :disabled))\n                                     htree)]\n        (is (= :input (-> selection first :tag)))))\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/child (select/tag :div)\n                                                   (select/class :foo)\n                                                   (select/attr :disabled))\n                                     htree)]\n        (is (= [] selection))))))\n\n(deftest has-child-test\n  (testing \"has-child selector combinator\"\n    (let [docs [\"<div id=\\\"outermost\\\"><div><span id=\\\"innermost\\\"></span></div></div>\"\n                \"<div id=\\\"outermost\\\"><div><span id=\\\"innermost\\\"></span></div><span id=\\\"sib\\\"></span></div>\"\n                \"<div id=\\\"outermost\\\"><span id=\\\"sib\\\"></span><div><span id=\\\"innermost\\\"></span></div></div>\"]]\n      (doseq [doc docs]\n        (let [htree (-> doc\n                     hickory/parse hickory/as-hickory)]\n          (let [selection (select/select (select/has-child\n                                          (select/id :innermost))\n                                         htree)]\n            (is (and (= 1 (count selection))\n                     (every? true? (map #(= :div (-> % :tag)) selection)))))\n          ;; Check that a descendant selector can peer up past the\n          ;; node having its descendants examined.\n          (let [selection (select/select (select/has-child\n                                          (select/descendant (select/id :outermost)\n                                                             (select/id :innermost)))\n                                         htree)]\n            (is (and (= 1 (count selection))\n                     (every? true? (map #(= :div (-> % :tag)) selection)))))\n          (let [selection (select/select (select/has-child (select/tag :a))\n                                         htree)]\n            (is (= [] selection))))))))\n\n(deftest parent-test\n  (testing \"parent selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/parent (select/el-not select/any))\n                                     htree)]\n        (is (= [] selection)))\n      (let [selection (select/select (select/parent (select/tag :html)\n                                                    (select/tag :div)\n                                                    (select/tag :span))\n                                     htree)]\n        (is (= [] selection)))\n      (let [selection (select/select (select/parent (select/tag :body)\n                                                    (select/tag :div)\n                                                    (select/tag :span))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (every? true? (map #(= :body (:tag %)) selection)))))\n      (let [selection (select/select (select/parent (select/tag :div)\n                                                    select/any)\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (every? true? (map #(= :div (-> % :tag))\n                                    selection)))))\n      ;; Find any element that is a parent of another element\n      (let [selection (select/select (select/parent select/any select/any)\n                                     htree)]\n        (is (and (= 4 (count selection))\n                 (every? true? (mapv #(or (= :html (-> % :tag))\n                                          (= :body (-> % :tag))\n                                          (= :div (-> % :tag))\n                                          (= :span (-> % :tag)))\n                                     selection))))))\n    ;; Check examples from the doc string.\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><input disabled></input></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/parent (select/tag :div)\n                                                    (select/class :foo)\n                                                    (select/attr :disabled))\n                                     htree)]\n        (is (= :div (-> selection first :tag)))))\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/parent (select/tag :div)\n                                                    (select/class :foo)\n                                                    (select/attr :disabled))\n                                     htree)]\n        (is (= [] selection))))))\n\n(deftest follow-adjacent-test\n  (testing \"follow-adjacent selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/follow-adjacent (select/tag :head)\n                                                             (select/tag :body))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :body (-> selection first :tag))))))\n    ;; Check the examples from the doc string.\n    (let [htree (-> \"<div>...</div><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/follow-adjacent (select/tag :div)\n                                                             (select/class \"foo\"))\n                                     htree)]\n        (is (= :span (-> selection first :tag)))))\n    (let [htree (-> \"<div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/follow-adjacent (select/tag :div)\n                                                             (select/class \"foo\"))\n                                     htree)]\n        (is (= [] selection))))))\n\n(deftest precede-adjacent-test\n  (testing \"precede-adjacent selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/precede-adjacent (select/tag :head)\n                                                              (select/tag :body))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :head (-> selection first :tag))))))\n    ;; Check the examples from the doc string.\n    (let [htree (-> \"<div>...</div><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/precede-adjacent (select/tag :div)\n                                                             (select/class \"foo\"))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :div (-> selection first :tag))))))\n    (let [htree (-> \"<div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/precede-adjacent (select/tag :div)\n                                                              (select/class \"foo\"))\n                                     htree)]\n        (is (= [] selection))))))\n\n(deftest ordered-test\n  ;; Just a basic tire kick here, it gets exercised by descendant, follow, and\n  ;; precede.\n  (testing \"ordered selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select\n                       (select/ordered\n                        #(select/left-of-node-type % :element)\n                        (select/tag :body)\n                        (select/tag :head))\n                       htree)]\n        (is (and (= 1 (count selection))\n                 (= :body (-> selection first :tag))))))))\n\n(deftest descendant-test\n  (testing \"descendant selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/descendant (select/tag :h1))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :h1 (-> selection first :tag)))))\n      (let [selection (select/select (select/descendant (select/class \"cool\")\n                                                        (select/tag :div))\n                                     htree)]\n        (is (= 1 (count selection))\n            (= \"deepestdiv\" (-> selection first :attrs :id))))\n      (let [selection (select/select (select/descendant (select/tag :div)\n                                                        select/any)\n                                     htree)]\n        (is (= 3 (count selection)))))\n    ;; Check examples from doc string.\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><input disabled></input></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/descendant (select/tag :div)\n                                                        (select/class :foo)\n                                                        (select/attr :disabled))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :input (-> selection first :tag))))))\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/descendant (select/tag :div)\n                                                        (select/class :foo)\n                                                        (select/attr :disabled))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :input (-> selection first :tag))))))))\n\n(deftest has-descendant-test\n  (testing \"has-descendant selector combinator\"\n    (let [docs [\"<div id=\\\"outermost\\\"><div><span id=\\\"innermost\\\"></span></div></div>\"\n                \"<div id=\\\"outermost\\\"><div><span id=\\\"innermost\\\"></span></div><span id=\\\"sib\\\"></span></div>\"\n                \"<div id=\\\"outermost\\\"><span id=\\\"sib\\\"></span><div><span id=\\\"innermost\\\"></span></div></div>\"]]\n      (doseq [doc docs]\n        (let [htree (-> doc\n                     hickory/parse hickory/as-hickory)]\n          (let [selection (select/select (select/and (select/tag :div)\n                                                     (select/has-descendant\n                                                      (select/id :innermost)))\n                                         htree)]\n            (is (and (= 2 (count selection))\n                     (every? true? (map #(= :div (-> % :tag)) selection)))))\n          ;; Check that a descendant selector can peer up past the\n          ;; node having its descendants examined.\n          (let [selection (select/select (select/and (select/tag :div)\n                                                     (select/has-descendant\n                                                      (select/descendant (select/id :outermost)\n                                                                         (select/tag :span))))\n                                         htree)]\n            (is (and (= 2 (count selection))\n                     (every? true? (map #(= :div (-> % :tag)) selection)))))\n          (let [selection (select/select (select/has-descendant (select/tag :a))\n                                         htree)]\n            (is (= [] selection))))))))\n\n(deftest ancestor-test\n  (testing \"ancestor selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/ancestor (select/tag :h1))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :h1 (-> selection first :tag)))))\n      (let [selection (select/select (select/ancestor (select/class \"cool\")\n                                                      (select/tag :div))\n                                     htree)]\n        (is (= 1 (count selection))\n            (= \"deepestdiv\" (-> selection first :attrs :id))))\n      (let [selection (select/select (select/ancestor (select/tag :div)\n                                                      select/any)\n                                     htree)]\n        (is (= 1 (count selection))))\n      (let [selection (select/select (select/ancestor (select/tag :span))\n                                     htree)]\n        (is (= 2 (count selection))))\n      ;; Find any element that is a parent of another element\n      (let [selection (select/select (select/parent select/any select/any)\n                                     htree)]\n        (is (and (= 4 (count selection))\n                 (every? true? (mapv #(or (= :html (-> % :tag))\n                                          (= :body (-> % :tag))\n                                          (= :div (-> % :tag))\n                                          (= :span (-> % :tag)))\n                                     selection))))))\n    ;; Check examples from doc string.\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><input disabled></input></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/ancestor (select/tag :div)\n                                                      (select/class :foo)\n                                                      (select/attr :disabled))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :div (-> selection first :tag))))))\n    (let [htree (-> \"<div><span class=\\\"foo\\\"><b><input disabled></input></b></span></div>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/ancestor (select/tag :div)\n                                                      (select/class :foo)\n                                                      (select/attr :disabled))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :div (-> selection first :tag))))))))\n\n(deftest follow-test\n  (testing \"follow selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/follow (select/tag :head)\n                                                    (select/tag :body))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :body (-> selection first :tag))))))\n    ;; Check the examples from the doc string.\n    (let [htree (-> \"<div>...</div><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/follow (select/tag :div)\n                                                    (select/class \"foo\"))\n                                     htree)]\n        (is (= :span (-> selection first :tag)))))\n    (let [htree (-> \"<div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/follow (select/tag :div)\n                                                    (select/class \"foo\"))\n                                     htree)]\n        (is (= :span (-> selection first :tag)))))))\n\n(deftest precede-test\n  (testing \"precede selector combinator\"\n    (let [htree (hickory/as-hickory (hickory/parse html1))]\n      (let [selection (select/select (select/precede (select/tag :head)\n                                                     (select/tag :body))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :head (-> selection first :tag))))))\n    ;; Check the examples from the doc string.\n    (let [htree (-> \"<div>...</div><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/precede (select/tag :div)\n                                                     (select/class \"foo\"))\n                                     htree)]\n        (is (and (= 1 (count selection))\n                 (= :div (-> selection first :tag))))))\n    (let [htree (-> \"<div>...</div><b>...</b><span class=\\\"foo\\\">...</span>\"\n                    hickory/parse hickory/as-hickory)]\n      (let [selection (select/select (select/precede (select/tag :div)\n                                                     (select/class \"foo\"))\n                                     htree)]\n        (is (= :div (-> selection first :tag)))))))\n\n(deftest graceful-boundaries-test\n  ;; Testing some problematic expressions to make sure they gracefully\n  ;; return empty results.\n  (let [hick (-> (hickory/parse-fragment \"<a><img href=\\\"\\\"/></a>\")\n                 first\n                 hickory/as-hickory)]\n    (is (= []\n           (select/select (select/child\n                           (select/follow-adjacent (select/tag :a)\n                                                   (select/tag :img)))\n                          hick)))\n    (is (= []\n           (select/select (select/child\n                           (select/follow-adjacent (select/tag :nonexistent)\n                                                   (select/tag :img)))\n                          hick)))\n    (is (= []\n           (select/select (select/child\n                           (select/follow-adjacent (select/tag :a)\n                                                   (select/tag :nonexistent)))\n                          hick)))\n    (is (= [{:type :element, :attrs {:href \"\"}, :tag :img, :content nil}]\n           (select/select (select/child select/first-child) hick)))\n    (is (= [{:type :element, :attrs {:href \"\"}, :tag :img, :content nil}]\n           (select/select (select/child select/last-child) hick)))))\n"
  },
  {
    "path": "test/cljc/hickory/test/zip.cljc",
    "content": "(ns hickory.test.zip\n  (:require [clojure.zip :as zip]\n            [hickory.core :refer [as-hiccup as-hickory parse]]\n            [hickory.zip :refer [hickory-zip hiccup-zip]]\n            [clojure.test :refer [deftest is]]))\n\n(deftest hickory-zipper\n  (is (= {:type :document,\n          :content [{:type :element,\n                     :attrs nil,\n                     :tag :html,\n                     :content [{:type :element,\n                                :attrs nil,\n                                :tag :head,\n                                :content nil}\n                               {:type :element,\n                                :attrs nil,\n                                :tag :body,\n                                :content [{:type :element,\n                                           :attrs nil,\n                                           :tag :a,\n                                           :content nil}]}]}]}\n         (zip/node (hickory-zip (as-hickory (parse \"<a>\"))))))\n  (is (= {:type :element,\n          :attrs nil,\n          :tag :html,\n          :content [{:type :element,\n                     :attrs nil,\n                     :tag :head,\n                     :content nil}\n                    {:type :element,\n                     :attrs nil,\n                     :tag :body,\n                     :content [{:type :element,\n                                :attrs nil,\n                                :tag :a,\n                                :content nil}]}]}\n         (-> (hickory-zip (as-hickory (parse \"<a>\")))\n             zip/next zip/node)))\n  (is (= {:type :element, :attrs nil, :tag :head, :content nil}\n       (-> (hickory-zip (as-hickory (parse \"<a>\")))\n           zip/next zip/next zip/node)))\n  (is (= {:type :element,\n          :attrs nil,\n          :tag :body,\n          :content [{:type :element,\n                     :attrs nil,\n                     :tag :a,\n                     :content nil}]}\n         (-> (hickory-zip (as-hickory (parse \"<a>\")))\n             zip/next zip/next zip/next zip/node)))\n  (is (= {:type :element,\n          :attrs nil,\n          :tag :html,\n          :content [{:type :element,\n                     :attrs nil,\n                     :tag :head,\n                     :content nil}\n                    {:type :element,\n                     :attrs nil,\n                     :tag :body,\n                     :content [{:type :element,\n                                :attrs nil,\n                                :tag :a,\n                                :content nil}]}]}\n         (-> (hickory-zip (as-hickory (parse \"<a>\")))\n             zip/next zip/next zip/next zip/up zip/node))))\n\n(deftest hiccup-zipper\n  (is (= '([:html {} [:head {}] [:body {} [:a {}]]])\n         (zip/node (hiccup-zip (as-hiccup (parse \"<a>\"))))))\n  (is (= [:html {} [:head {}] [:body {} [:a {}]]]\n         (-> (hiccup-zip (as-hiccup (parse \"<a>\")))\n             zip/next zip/node)))\n  (is (= [:head {}]\n         (-> (hiccup-zip (as-hiccup (parse \"<a>\")))\n             zip/next zip/next zip/node)))\n  (is (= [:body {} [:a {}]]\n         (-> (hiccup-zip (as-hiccup (parse \"<a>\")))\n             zip/next zip/next zip/next zip/node)))\n  (is (= [:html {} [:head {}] [:body {} [:a {}]]]\n         (-> (hiccup-zip (as-hiccup (parse \"<a>\")))\n             zip/next zip/next zip/next zip/up zip/node))))\n"
  },
  {
    "path": "test/cljs/hickory/advanced.edn",
    "content": "{:optimizations :advanced}\n"
  },
  {
    "path": "test/cljs/hickory/doo_runner.cljs",
    "content": "(ns hickory.doo-runner\n  (:require [doo.runner :refer-macros [doo-tests]]\n            [hickory.test.convert]\n            [hickory.test.core]\n            [hickory.test.hiccup-utils]\n            [hickory.test.render]\n            [hickory.test.select]\n            [hickory.test.zip]))\n\n(doo-tests 'hickory.test.core\n           'hickory.test.convert\n           'hickory.test.hiccup-utils\n           'hickory.test.render\n           'hickory.test.select\n           'hickory.test.zip)\n\n"
  }
]