├── .esdoc.json ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── linter.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── ast │ └── source │ │ ├── .external-ecmascript.js.json │ │ ├── Parser.js.json │ │ ├── errors │ │ ├── NotImplemented.js.json │ │ └── ParserError.js.json │ │ ├── index.js.json │ │ └── strategies │ │ ├── Base.js.json │ │ ├── Csv.js.json │ │ ├── Json.js.json │ │ ├── Xml.js.json │ │ ├── Xml │ │ ├── XmlTag.js.json │ │ └── index.js.json │ │ ├── Yaml.js.json │ │ └── index.js.json ├── badge.svg ├── coverage.json ├── css │ ├── github.css │ ├── identifiers.css │ ├── manual.css │ ├── prettify-tomorrow.css │ ├── search.css │ ├── source.css │ ├── style.css │ └── test.css ├── file │ └── src │ │ ├── Parser.js.html │ │ ├── errors │ │ ├── NotImplemented.js.html │ │ └── ParserError.js.html │ │ ├── index.js.html │ │ └── strategies │ │ ├── Base.js.html │ │ ├── Csv.js.html │ │ ├── Json.js.html │ │ ├── Xml.js.html │ │ ├── Xml │ │ ├── XmlTag.js.html │ │ └── index.js.html │ │ ├── Yaml.js.html │ │ └── index.js.html ├── identifiers.html ├── image │ ├── badge.svg │ ├── esdoc-logo-mini-black.png │ ├── esdoc-logo-mini.png │ ├── github.png │ ├── manual-badge.svg │ └── search.png ├── index.html ├── index.json ├── inject │ └── css │ │ └── 0-parser.css ├── lint.json ├── manual │ ├── README.html │ ├── csv.html │ ├── index.html │ ├── json.html │ ├── xml.html │ └── yaml.html ├── script │ ├── inherited-summary.js │ ├── inner-link.js │ ├── manual.js │ ├── patch-for-local.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js │ ├── pretty-print.js │ ├── search.js │ ├── search_index.js │ └── test-summary.js └── source.html ├── esdocs ├── css │ └── parser.css └── manual │ ├── csv.md │ ├── json.md │ ├── xml.md │ └── yaml.md ├── package-lock.json ├── package.json ├── src ├── Parser.js ├── errors │ ├── NotImplemented.js │ └── ParserError.js ├── index.js └── strategies │ ├── Base.js │ ├── Csv.js │ ├── Json.js │ ├── Xml │ ├── XmlTag.js │ └── index.js │ ├── Yaml.js │ └── index.js └── tests ├── Parser.test.js ├── data └── services.json └── strategies ├── Base.test.js ├── Csv.test.js ├── Json.test.js ├── Xml.test.js └── Yaml.test.js /.esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "plugins": [ 5 | { 6 | "name": "esdoc-standard-plugin", 7 | "brand": { 8 | "title": "parser - the all-in-one filetype parser for javascript", 9 | "description": "The easiest parser for JSON, XML, CSV and YAML. Use it as simple as JSON.stringify() or JSON.parse(). All in one place." 10 | }, 11 | "option": { 12 | "manual": { 13 | "index": "./README.md", 14 | "asset": "", 15 | "globalIndex": true, 16 | "files": [ 17 | "./esdocs/manual/json.md", 18 | "./esdocs/manual/csv.md", 19 | "./esdocs/manual/xml.md", 20 | "./esdocs/manual/yaml.md" 21 | ] 22 | } 23 | } 24 | }, 25 | { 26 | "name": "esdoc-ecmascript-proposal-plugin", 27 | "option": { "all": true } 28 | }, 29 | { 30 | "name": "esdoc-inject-style-plugin", 31 | "option": { 32 | "enable": true, 33 | "styles": [ 34 | "./esdocs/css/parser.css" 35 | ] 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "jest": true, 6 | "node": true 7 | }, 8 | "extends": [ 9 | "standard" 10 | ], 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parserOptions": { 16 | "ecmaVersion": 11 17 | }, 18 | "rules": { 19 | }, 20 | "ignorePatterns": ["docs/**"] 21 | } 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: linter 2 | on: push 3 | jobs: 4 | linter: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - name: Install modules 9 | run: npm install 10 | - name: Run linter 11 | run: npm run lint 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - name: Install modules 9 | run: npm install 10 | - name: Run tests 11 | run: npm run test 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | nohup.out 4 | parserblade.tar.gz 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Matheus Hernandes (onhernandes.github.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parserblade 2 | 3 | ![CI](https://github.com/onhernandes/parserblade/workflows/CI/badge.svg?branch=main) 4 | 5 | A all-in-one parser for Javascript, heavily inspired by: https://github.com/nathanmac/Parser. It allows you to work with JSON, XML, CSV and YAML more without worrying about which module install. It's designed to work just as `JSON.parse` and `JSON.stringify` does, with some improvements. 6 | 7 | See [docs](https://onhernandes.github.io/parserblade) for more info and examples. 8 | 9 | ## Installation 10 | 11 | ```sh 12 | $ npm install --save parserblade 13 | ``` 14 | 15 | ## Usage 16 | 17 | Every filetype has two main methods: `stringify()` and `parse()`, both receives two arguments, `data` containing any type of data and an options object. 18 | 19 | ### CSV 20 | 21 | ```javascript 22 | const { csv } = require('parserblade') 23 | 24 | // Parsing 25 | const input = 'title,platform\nStardew Valley,Steam' 26 | const result = csv.parse(input) 27 | console.log(result) // [ { title: 'Stardew Valley', platform: 'Steam' } ] 28 | 29 | // Stringifying 30 | console.log( 31 | csv.stringify(result) 32 | ) // 'title,platform\nStardew Valley,Steam' 33 | ``` 34 | 35 | ### YAML 36 | 37 | ```javascript 38 | const { yaml } = require('parserblade') 39 | 40 | // Parsing 41 | const input = 'title: Stardew Valley\nplatform: Steam' 42 | const result = yaml.parse(input) 43 | console.log(result) // { title: 'Stardew Valley', platform: 'Steam' } 44 | 45 | // Stringifying 46 | console.log( 47 | yaml.stringify(result) 48 | ) // 'title: Stardew Valley\nplatform: Steam' 49 | ``` 50 | 51 | ### XML 52 | 53 | ```javascript 54 | const { xml } = require('parserblade') 55 | 56 | // Parsing 57 | const input = 'lodash' 58 | const result = xml.parse(input) 59 | console.log(result) // { package: 'lodash' } 60 | 61 | // Stringifying 62 | console.log( 63 | xml.stringify(result) 64 | ) // 'lodash' 65 | ``` 66 | 67 | ## License 68 | 69 | MIT © 70 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | 51% 15 | 51% 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": "51.47%", 3 | "expectCount": 68, 4 | "actualCount": 35, 5 | "files": { 6 | "src/Parser.js": { 7 | "expectCount": 8, 8 | "actualCount": 6, 9 | "undocumentLines": [ 10 | 55, 11 | 57 12 | ] 13 | }, 14 | "src/errors/NotImplemented.js": { 15 | "expectCount": 1, 16 | "actualCount": 1, 17 | "undocumentLines": [] 18 | }, 19 | "src/errors/ParserError.js": { 20 | "expectCount": 1, 21 | "actualCount": 1, 22 | "undocumentLines": [] 23 | }, 24 | "src/index.js": { 25 | "expectCount": 2, 26 | "actualCount": 0, 27 | "undocumentLines": [ 28 | 1, 29 | 7 30 | ] 31 | }, 32 | "src/strategies/Base.js": { 33 | "expectCount": 9, 34 | "actualCount": 7, 35 | "undocumentLines": [ 36 | 1, 37 | 2 38 | ] 39 | }, 40 | "src/strategies/Csv.js": { 41 | "expectCount": 11, 42 | "actualCount": 5, 43 | "undocumentLines": [ 44 | 1, 45 | 2, 46 | 3, 47 | 4, 48 | 5, 49 | 6 50 | ] 51 | }, 52 | "src/strategies/Json.js": { 53 | "expectCount": 8, 54 | "actualCount": 5, 55 | "undocumentLines": [ 56 | 1, 57 | 2, 58 | 3 59 | ] 60 | }, 61 | "src/strategies/Xml/XmlTag.js": { 62 | "expectCount": 4, 63 | "actualCount": 0, 64 | "undocumentLines": [ 65 | 1, 66 | 7, 67 | 14, 68 | 18 69 | ] 70 | }, 71 | "src/strategies/Xml/index.js": { 72 | "expectCount": 14, 73 | "actualCount": 7, 74 | "undocumentLines": [ 75 | 1, 76 | 2, 77 | 3, 78 | 4, 79 | 5, 80 | 6, 81 | 7 82 | ] 83 | }, 84 | "src/strategies/Yaml.js": { 85 | "expectCount": 6, 86 | "actualCount": 3, 87 | "undocumentLines": [ 88 | 1, 89 | 2, 90 | 3 91 | ] 92 | }, 93 | "src/strategies/index.js": { 94 | "expectCount": 4, 95 | "actualCount": 0, 96 | "undocumentLines": [ 97 | 1, 98 | 2, 99 | 3, 100 | 4 101 | ] 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /docs/css/github.css: -------------------------------------------------------------------------------- 1 | /* github markdown */ 2 | .github-markdown { 3 | font-size: 16px; 4 | } 5 | 6 | .github-markdown h1, 7 | .github-markdown h2, 8 | .github-markdown h3, 9 | .github-markdown h4, 10 | .github-markdown h5 { 11 | margin-top: 1em; 12 | margin-bottom: 16px; 13 | font-weight: bold; 14 | padding: 0; 15 | } 16 | 17 | .github-markdown h1:nth-of-type(1) { 18 | margin-top: 0; 19 | } 20 | 21 | .github-markdown h1 { 22 | font-size: 2em; 23 | padding-bottom: 0.3em; 24 | } 25 | 26 | .github-markdown h2 { 27 | font-size: 1.75em; 28 | padding-bottom: 0.3em; 29 | } 30 | 31 | .github-markdown h3 { 32 | font-size: 1.5em; 33 | } 34 | 35 | .github-markdown h4 { 36 | font-size: 1.25em; 37 | } 38 | 39 | .github-markdown h5 { 40 | font-size: 1em; 41 | } 42 | 43 | .github-markdown ul, .github-markdown ol { 44 | padding-left: 2em; 45 | } 46 | 47 | .github-markdown pre > code { 48 | font-size: 0.85em; 49 | } 50 | 51 | .github-markdown table { 52 | margin-bottom: 1em; 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | 57 | .github-markdown table tr { 58 | background-color: #fff; 59 | border-top: 1px solid #ccc; 60 | } 61 | 62 | .github-markdown table th, 63 | .github-markdown table td { 64 | padding: 6px 13px; 65 | border: 1px solid #ddd; 66 | } 67 | 68 | .github-markdown table tr:nth-child(2n) { 69 | background-color: #f8f8f8; 70 | } 71 | 72 | .github-markdown hr { 73 | border-right: 0; 74 | border-bottom: 1px solid #e5e5e5; 75 | border-left: 0; 76 | border-top: 0; 77 | } 78 | 79 | /** badge(.svg) does not have border */ 80 | .github-markdown img:not([src*=".svg"]) { 81 | max-width: 100%; 82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5); 83 | } 84 | -------------------------------------------------------------------------------- /docs/css/identifiers.css: -------------------------------------------------------------------------------- 1 | .identifiers-wrap { 2 | display: flex; 3 | align-items: flex-start; 4 | } 5 | 6 | .identifier-dir-tree { 7 | background: #fff; 8 | border: solid 1px #ddd; 9 | border-radius: 0.25em; 10 | top: 52px; 11 | position: -webkit-sticky; 12 | position: sticky; 13 | max-height: calc(100vh - 155px); 14 | overflow-y: scroll; 15 | min-width: 200px; 16 | margin-left: 1em; 17 | } 18 | 19 | .identifier-dir-tree-header { 20 | padding: 0.5em; 21 | background-color: #fafafa; 22 | border-bottom: solid 1px #ddd; 23 | } 24 | 25 | .identifier-dir-tree-content { 26 | padding: 0 0.5em 0; 27 | } 28 | 29 | .identifier-dir-tree-content > div { 30 | padding-top: 0.25em; 31 | padding-bottom: 0.25em; 32 | } 33 | 34 | .identifier-dir-tree-content a { 35 | color: inherit; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /docs/css/manual.css: -------------------------------------------------------------------------------- 1 | .github-markdown .manual-toc { 2 | padding-left: 0; 3 | } 4 | 5 | .manual-index .manual-cards { 6 | display: flex; 7 | flex-wrap: wrap; 8 | } 9 | 10 | .manual-index .manual-card-wrap { 11 | width: 280px; 12 | padding: 10px 20px 10px 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | .manual-index .manual-card-wrap > h1 { 17 | margin: 0; 18 | font-size: 1em; 19 | font-weight: 600; 20 | padding: 0.2em 0 0.2em 0.5em; 21 | border-radius: 0.1em 0.1em 0 0; 22 | border: none; 23 | } 24 | 25 | .manual-index .manual-card-wrap > h1 span { 26 | color: #555; 27 | } 28 | 29 | .manual-index .manual-card { 30 | height: 200px; 31 | overflow: hidden; 32 | border: solid 1px rgba(230, 230, 230, 0.84); 33 | border-radius: 0 0 0.1em 0.1em; 34 | padding: 8px; 35 | position: relative; 36 | } 37 | 38 | .manual-index .manual-card > div { 39 | transform: scale(0.4); 40 | transform-origin: 0 0; 41 | width: 250%; 42 | } 43 | 44 | .manual-index .manual-card > a { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | width: 100%; 49 | height: 100%; 50 | background: rgba(210, 210, 210, 0.1); 51 | } 52 | 53 | .manual-index .manual-card > a:hover { 54 | background: none; 55 | } 56 | 57 | .manual-index .manual-badge { 58 | margin: 0; 59 | } 60 | 61 | .manual-index .manual-user-index { 62 | margin-bottom: 1em; 63 | border-bottom: solid 1px #ddd; 64 | } 65 | 66 | .manual-root .navigation { 67 | padding-left: 4px; 68 | margin-top: 4px; 69 | } 70 | 71 | .navigation .manual-toc-root > div { 72 | padding-left: 0.25em; 73 | padding-right: 0.75em; 74 | } 75 | 76 | .github-markdown .manual-toc-title a { 77 | color: inherit; 78 | } 79 | 80 | .manual-breadcrumb-list { 81 | font-size: 0.8em; 82 | margin-bottom: 1em; 83 | } 84 | 85 | .manual-toc-title a:hover { 86 | color: #039BE5; 87 | } 88 | 89 | .manual-toc li { 90 | margin: 0.75em 0; 91 | list-style-type: none; 92 | } 93 | 94 | .navigation .manual-toc [class^="indent-h"] a { 95 | color: #666; 96 | } 97 | 98 | .navigation .manual-toc .indent-h1 a { 99 | color: #555; 100 | font-weight: 600; 101 | display: block; 102 | } 103 | 104 | .manual-toc .indent-h1 { 105 | display: block; 106 | margin: 0.4em 0 0 0.25em; 107 | padding: 0.2em 0 0.2em 0.5em; 108 | border-radius: 0.1em; 109 | } 110 | 111 | .manual-root .navigation .manual-toc li:not(.indent-h1) { 112 | margin-top: 0.5em; 113 | } 114 | 115 | .manual-toc .indent-h2 { 116 | display: none; 117 | margin-left: 1.5em; 118 | } 119 | .manual-toc .indent-h3 { 120 | display: none; 121 | margin-left: 2.5em; 122 | } 123 | .manual-toc .indent-h4 { 124 | display: none; 125 | margin-left: 3.5em; 126 | } 127 | .manual-toc .indent-h5 { 128 | display: none; 129 | margin-left: 4.5em; 130 | } 131 | 132 | .manual-nav li { 133 | margin: 0.75em 0; 134 | } 135 | -------------------------------------------------------------------------------- /docs/css/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/css/search.css: -------------------------------------------------------------------------------- 1 | /* search box */ 2 | .search-box { 3 | position: absolute; 4 | top: 10px; 5 | right: 50px; 6 | padding-right: 8px; 7 | padding-bottom: 10px; 8 | line-height: normal; 9 | font-size: 12px; 10 | } 11 | 12 | .search-box img { 13 | width: 20px; 14 | vertical-align: top; 15 | } 16 | 17 | .search-input { 18 | display: inline; 19 | visibility: hidden; 20 | width: 0; 21 | padding: 2px; 22 | height: 1.5em; 23 | outline: none; 24 | background: transparent; 25 | border: 1px #0af; 26 | border-style: none none solid none; 27 | vertical-align: bottom; 28 | } 29 | 30 | .search-input-edge { 31 | display: none; 32 | width: 1px; 33 | height: 5px; 34 | background-color: #0af; 35 | vertical-align: bottom; 36 | } 37 | 38 | .search-result { 39 | position: absolute; 40 | display: none; 41 | height: 600px; 42 | width: 100%; 43 | padding: 0; 44 | margin-top: 5px; 45 | margin-left: 24px; 46 | background: white; 47 | box-shadow: 1px 1px 4px rgb(0,0,0); 48 | white-space: nowrap; 49 | overflow-y: scroll; 50 | } 51 | 52 | .search-result-import-path { 53 | color: #aaa; 54 | font-size: 12px; 55 | } 56 | 57 | .search-result li { 58 | list-style: none; 59 | padding: 2px 4px; 60 | } 61 | 62 | .search-result li a { 63 | display: block; 64 | } 65 | 66 | .search-result li.selected { 67 | background: #ddd; 68 | } 69 | 70 | .search-result li.search-separator { 71 | background: rgb(37, 138, 175); 72 | color: white; 73 | } 74 | 75 | .search-box.active .search-input { 76 | visibility: visible; 77 | transition: width 0.2s ease-out; 78 | width: 300px; 79 | } 80 | 81 | .search-box.active .search-input-edge { 82 | display: inline-block; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /docs/css/source.css: -------------------------------------------------------------------------------- 1 | table.files-summary { 2 | width: 100%; 3 | margin: 10px 0; 4 | border-spacing: 0; 5 | border: 0; 6 | border-collapse: collapse; 7 | text-align: right; 8 | } 9 | 10 | table.files-summary tbody tr:hover { 11 | background: #eee; 12 | } 13 | 14 | table.files-summary td:first-child, 15 | table.files-summary td:nth-of-type(2) { 16 | text-align: left; 17 | } 18 | 19 | table.files-summary[data-use-coverage="false"] td.coverage { 20 | display: none; 21 | } 22 | 23 | table.files-summary thead { 24 | background: #fafafa; 25 | } 26 | 27 | table.files-summary td { 28 | border: solid 1px #ddd; 29 | padding: 4px 10px; 30 | vertical-align: top; 31 | } 32 | 33 | table.files-summary td.identifiers > span { 34 | display: block; 35 | margin-top: 4px; 36 | } 37 | table.files-summary td.identifiers > span:first-child { 38 | margin-top: 0; 39 | } 40 | 41 | table.files-summary .coverage-count { 42 | font-size: 12px; 43 | color: #aaa; 44 | display: inline-block; 45 | min-width: 40px; 46 | } 47 | 48 | .total-coverage-count { 49 | position: relative; 50 | bottom: 2px; 51 | font-size: 12px; 52 | color: #666; 53 | font-weight: 500; 54 | padding-left: 5px; 55 | } 56 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto:400,300,700); 2 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,600,700); 3 | @import url(./manual.css); 4 | @import url(./source.css); 5 | @import url(./test.css); 6 | @import url(./identifiers.css); 7 | @import url(./github.css); 8 | @import url(./search.css); 9 | 10 | * { 11 | margin: 0; 12 | padding: 0; 13 | text-decoration: none; 14 | } 15 | 16 | html 17 | { 18 | font-family: 'Source Sans Pro', 'Roboto', sans-serif; 19 | overflow: auto; 20 | /*font-size: 14px;*/ 21 | /*color: #4d4e53;*/ 22 | /*color: rgba(0, 0, 0, .68);*/ 23 | color: #555; 24 | background-color: #fff; 25 | } 26 | 27 | a { 28 | /*color: #0095dd;*/ 29 | /*color:rgb(37, 138, 175);*/ 30 | color: #039BE5; 31 | } 32 | 33 | code a:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | ul, ol { 38 | padding-left: 20px; 39 | } 40 | 41 | ul li { 42 | list-style: disc; 43 | margin: 4px 0; 44 | } 45 | 46 | ol li { 47 | margin: 4px 0; 48 | } 49 | 50 | h1 { 51 | margin-bottom: 10px; 52 | font-size: 34px; 53 | font-weight: 300; 54 | border-bottom: solid 1px #ddd; 55 | } 56 | 57 | h2 { 58 | margin-top: 24px; 59 | margin-bottom: 10px; 60 | font-size: 20px; 61 | border-bottom: solid 1px #ddd; 62 | font-weight: 300; 63 | } 64 | 65 | h3 { 66 | position: relative; 67 | font-size: 16px; 68 | margin-bottom: 12px; 69 | padding: 4px; 70 | font-weight: 300; 71 | } 72 | 73 | details { 74 | cursor: pointer; 75 | } 76 | 77 | del { 78 | text-decoration: line-through; 79 | } 80 | 81 | p { 82 | margin-bottom: 15px; 83 | line-height: 1.5; 84 | } 85 | 86 | code { 87 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 88 | } 89 | 90 | pre > code { 91 | display: block; 92 | } 93 | 94 | pre.prettyprint, pre > code { 95 | padding: 4px; 96 | margin: 1em 0; 97 | background-color: #f5f5f5; 98 | border-radius: 3px; 99 | } 100 | 101 | pre.prettyprint > code { 102 | margin: 0; 103 | } 104 | 105 | p > code, 106 | li > code { 107 | padding: 0.2em 0.5em; 108 | margin: 0; 109 | font-size: 85%; 110 | background-color: rgba(0,0,0,0.04); 111 | border-radius: 3px; 112 | } 113 | 114 | .code { 115 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 116 | font-size: 13px; 117 | } 118 | 119 | .import-path pre.prettyprint, 120 | .import-path pre.prettyprint code { 121 | margin: 0; 122 | padding: 0; 123 | border: none; 124 | background: white; 125 | } 126 | 127 | .layout-container { 128 | /*display: flex;*/ 129 | /*flex-direction: row;*/ 130 | /*justify-content: flex-start;*/ 131 | /*align-items: stretch;*/ 132 | } 133 | 134 | .layout-container > header { 135 | display: flex; 136 | height: 40px; 137 | line-height: 40px; 138 | font-size: 16px; 139 | padding: 0 10px; 140 | margin: 0; 141 | position: fixed; 142 | width: 100%; 143 | z-index: 1; 144 | background-color: #fafafa; 145 | top: 0; 146 | border-bottom: solid 1px #ddd; 147 | } 148 | .layout-container > header > a{ 149 | margin: 0 5px; 150 | color: #444; 151 | } 152 | 153 | .layout-container > header > a.repo-url-github { 154 | font-size: 0; 155 | display: inline-block; 156 | width: 20px; 157 | height: 38px; 158 | background: url("../image/github.png") no-repeat center; 159 | background-size: 20px; 160 | vertical-align: top; 161 | } 162 | 163 | .navigation { 164 | position: fixed; 165 | top: 0; 166 | left: 0; 167 | box-sizing: border-box; 168 | width: 250px; 169 | height: 100%; 170 | padding-top: 40px; 171 | padding-left: 15px; 172 | padding-bottom: 2em; 173 | margin-top:1em; 174 | overflow-x: scroll; 175 | box-shadow: rgba(255, 255, 255, 1) -1px 0 0 inset; 176 | border-right: 1px solid #ddd; 177 | } 178 | 179 | .navigation ul { 180 | padding: 0; 181 | } 182 | 183 | .navigation li { 184 | list-style: none; 185 | margin: 4px 0; 186 | white-space: nowrap; 187 | } 188 | 189 | .navigation li a { 190 | color: #666; 191 | } 192 | 193 | .navigation .nav-dir-path { 194 | display: block; 195 | margin-top: 0.7em; 196 | margin-bottom: 0.25em; 197 | font-weight: 600; 198 | } 199 | 200 | .kind-class, 201 | .kind-interface, 202 | .kind-function, 203 | .kind-typedef, 204 | .kind-variable, 205 | .kind-external { 206 | margin-left: 0.75em; 207 | width: 1.2em; 208 | height: 1.2em; 209 | display: inline-block; 210 | text-align: center; 211 | border-radius: 0.2em; 212 | margin-right: 0.2em; 213 | font-weight: bold; 214 | line-height: 1.2em; 215 | } 216 | 217 | .kind-class { 218 | color: #009800; 219 | background-color: #bfe5bf; 220 | } 221 | 222 | .kind-interface { 223 | color: #fbca04; 224 | background-color: #fef2c0; 225 | } 226 | 227 | .kind-function { 228 | color: #6b0090; 229 | background-color: #d6bdde; 230 | } 231 | 232 | .kind-variable { 233 | color: #eb6420; 234 | background-color: #fad8c7; 235 | } 236 | 237 | .kind-typedef { 238 | color: #db001e; 239 | background-color: #edbec3; 240 | } 241 | 242 | .kind-external { 243 | color: #0738c3; 244 | background-color: #bbcbea; 245 | } 246 | 247 | .summary span[class^="kind-"] { 248 | margin-left: 0; 249 | } 250 | 251 | h1 .version, 252 | h1 .url a { 253 | font-size: 14px; 254 | color: #aaa; 255 | } 256 | 257 | .content { 258 | margin-top: 40px; 259 | margin-left: 250px; 260 | padding: 10px 50px 10px 20px; 261 | } 262 | 263 | .header-notice { 264 | font-size: 14px; 265 | color: #aaa; 266 | margin: 0; 267 | } 268 | 269 | .expression-extends .prettyprint { 270 | margin-left: 10px; 271 | background: white; 272 | } 273 | 274 | .extends-chain { 275 | border-bottom: 1px solid#ddd; 276 | padding-bottom: 10px; 277 | margin-bottom: 10px; 278 | } 279 | 280 | .extends-chain span:nth-of-type(1) { 281 | padding-left: 10px; 282 | } 283 | 284 | .extends-chain > div { 285 | margin: 5px 0; 286 | } 287 | 288 | .description table { 289 | font-size: 14px; 290 | border-spacing: 0; 291 | border: 0; 292 | border-collapse: collapse; 293 | } 294 | 295 | .description thead { 296 | background: #999; 297 | color: white; 298 | } 299 | 300 | .description table td, 301 | .description table th { 302 | border: solid 1px #ddd; 303 | padding: 4px; 304 | font-weight: normal; 305 | } 306 | 307 | .flat-list ul { 308 | padding-left: 0; 309 | } 310 | 311 | .flat-list li { 312 | display: inline; 313 | list-style: none; 314 | } 315 | 316 | table.summary { 317 | width: 100%; 318 | margin: 10px 0; 319 | border-spacing: 0; 320 | border: 0; 321 | border-collapse: collapse; 322 | } 323 | 324 | table.summary thead { 325 | background: #fafafa; 326 | } 327 | 328 | table.summary td { 329 | border: solid 1px #ddd; 330 | padding: 4px 10px; 331 | } 332 | 333 | table.summary tbody td:nth-child(1) { 334 | text-align: right; 335 | white-space: nowrap; 336 | min-width: 64px; 337 | vertical-align: top; 338 | } 339 | 340 | table.summary tbody td:nth-child(2) { 341 | width: 100%; 342 | border-right: none; 343 | } 344 | 345 | table.summary tbody td:nth-child(3) { 346 | white-space: nowrap; 347 | border-left: none; 348 | vertical-align: top; 349 | } 350 | 351 | table.summary td > div:nth-of-type(2) { 352 | padding-top: 4px; 353 | padding-left: 15px; 354 | } 355 | 356 | table.summary td p { 357 | margin-bottom: 0; 358 | } 359 | 360 | .inherited-summary thead td { 361 | padding-left: 2px; 362 | } 363 | 364 | .inherited-summary thead a { 365 | color: white; 366 | } 367 | 368 | .inherited-summary .summary tbody { 369 | display: none; 370 | } 371 | 372 | .inherited-summary .summary .toggle { 373 | padding: 0 4px; 374 | font-size: 12px; 375 | cursor: pointer; 376 | } 377 | .inherited-summary .summary .toggle.closed:before { 378 | content: "▶"; 379 | } 380 | .inherited-summary .summary .toggle.opened:before { 381 | content: "▼"; 382 | } 383 | 384 | .member, .method { 385 | margin-bottom: 24px; 386 | } 387 | 388 | table.params { 389 | width: 100%; 390 | margin: 10px 0; 391 | border-spacing: 0; 392 | border: 0; 393 | border-collapse: collapse; 394 | } 395 | 396 | table.params thead { 397 | background: #eee; 398 | color: #aaa; 399 | } 400 | 401 | table.params td { 402 | padding: 4px; 403 | border: solid 1px #ddd; 404 | } 405 | 406 | table.params td p { 407 | margin: 0; 408 | } 409 | 410 | .content .detail > * { 411 | margin: 15px 0; 412 | } 413 | 414 | .content .detail > h3 { 415 | color: black; 416 | background-color: #f0f0f0; 417 | } 418 | 419 | .content .detail > div { 420 | margin-left: 10px; 421 | } 422 | 423 | .content .detail > .import-path { 424 | margin-top: -8px; 425 | } 426 | 427 | .content .detail + .detail { 428 | margin-top: 30px; 429 | } 430 | 431 | .content .detail .throw td:first-child { 432 | padding-right: 10px; 433 | } 434 | 435 | .content .detail h4 + :not(pre) { 436 | padding-left: 0; 437 | margin-left: 10px; 438 | } 439 | 440 | .content .detail h4 + ul li { 441 | list-style: none; 442 | } 443 | 444 | .return-param * { 445 | display: inline; 446 | } 447 | 448 | .argument-params { 449 | margin-bottom: 20px; 450 | } 451 | 452 | .return-type { 453 | padding-right: 10px; 454 | font-weight: normal; 455 | } 456 | 457 | .return-desc { 458 | margin-left: 10px; 459 | margin-top: 4px; 460 | } 461 | 462 | .return-desc p { 463 | margin: 0; 464 | } 465 | 466 | .deprecated, .experimental, .instance-docs { 467 | border-left: solid 5px orange; 468 | padding-left: 4px; 469 | margin: 4px 0; 470 | } 471 | 472 | tr.listen p, 473 | tr.throw p, 474 | tr.emit p{ 475 | margin-bottom: 10px; 476 | } 477 | 478 | .version, .since { 479 | color: #aaa; 480 | } 481 | 482 | h3 .right-info { 483 | position: absolute; 484 | right: 4px; 485 | font-size: 14px; 486 | } 487 | 488 | .version + .since:before { 489 | content: '| '; 490 | } 491 | 492 | .see { 493 | margin-top: 10px; 494 | } 495 | 496 | .see h4 { 497 | margin: 4px 0; 498 | } 499 | 500 | .content .detail h4 + .example-doc { 501 | margin: 6px 0; 502 | } 503 | 504 | .example-caption { 505 | position: relative; 506 | bottom: -1px; 507 | display: inline-block; 508 | padding: 4px; 509 | font-style: italic; 510 | background-color: #f5f5f5; 511 | font-weight: bold; 512 | border-radius: 3px; 513 | border-bottom-left-radius: 0; 514 | border-bottom-right-radius: 0; 515 | } 516 | 517 | .example-caption + pre.source-code { 518 | margin-top: 0; 519 | border-top-left-radius: 0; 520 | } 521 | 522 | footer, .file-footer { 523 | text-align: right; 524 | font-style: italic; 525 | font-weight: 100; 526 | font-size: 13px; 527 | margin-right: 50px; 528 | margin-left: 270px; 529 | border-top: 1px solid #ddd; 530 | padding-top: 30px; 531 | margin-top: 20px; 532 | padding-bottom: 10px; 533 | } 534 | 535 | footer img { 536 | width: 24px; 537 | vertical-align: middle; 538 | padding-left: 4px; 539 | position: relative; 540 | top: -3px; 541 | opacity: 0.6; 542 | } 543 | 544 | pre.source-code { 545 | padding: 4px; 546 | } 547 | 548 | pre.raw-source-code > code { 549 | padding: 0; 550 | margin: 0; 551 | font-size: 12px; 552 | background: #fff; 553 | border: solid 1px #ddd; 554 | line-height: 1.5; 555 | } 556 | 557 | pre.raw-source-code > code > ol { 558 | counter-reset:number; 559 | list-style:none; 560 | margin:0; 561 | padding:0; 562 | overflow: hidden; 563 | } 564 | 565 | pre.raw-source-code > code > ol li:before { 566 | counter-increment: number; 567 | content: counter(number); 568 | display: inline-block; 569 | min-width: 3em; 570 | color: #aaa; 571 | text-align: right; 572 | padding-right: 1em; 573 | } 574 | 575 | pre.source-code.line-number { 576 | padding: 0; 577 | } 578 | 579 | pre.source-code ol { 580 | background: #eee; 581 | padding-left: 40px; 582 | } 583 | 584 | pre.source-code li { 585 | background: white; 586 | padding-left: 4px; 587 | list-style: decimal; 588 | margin: 0; 589 | } 590 | 591 | pre.source-code.line-number li.active { 592 | background: rgb(255, 255, 150) !important; 593 | } 594 | 595 | pre.source-code.line-number li.error-line { 596 | background: #ffb8bf; 597 | } 598 | 599 | .inner-link-active { 600 | /*background: rgb(255, 255, 150) !important;*/ 601 | background: #039BE5 !important; 602 | color: #fff !important; 603 | padding-left: 0.1em !important; 604 | } 605 | 606 | .inner-link-active a { 607 | color: inherit; 608 | } 609 | -------------------------------------------------------------------------------- /docs/css/test.css: -------------------------------------------------------------------------------- 1 | table.test-summary thead { 2 | background: #fafafa; 3 | } 4 | 5 | table.test-summary thead .test-description { 6 | width: 50%; 7 | } 8 | 9 | table.test-summary { 10 | width: 100%; 11 | margin: 10px 0; 12 | border-spacing: 0; 13 | border: 0; 14 | border-collapse: collapse; 15 | } 16 | 17 | table.test-summary thead .test-count { 18 | width: 3em; 19 | } 20 | 21 | table.test-summary tbody tr:hover { 22 | background-color: #eee; 23 | } 24 | 25 | table.test-summary td { 26 | border: solid 1px #ddd; 27 | padding: 4px 10px; 28 | vertical-align: top; 29 | } 30 | 31 | table.test-summary td p { 32 | margin: 0; 33 | } 34 | 35 | table.test-summary tr.test-interface .toggle { 36 | display: inline-block; 37 | float: left; 38 | margin-right: 4px; 39 | cursor: pointer; 40 | font-size: 0.8em; 41 | padding-top: 0.25em; 42 | } 43 | 44 | table.test-summary tr.test-interface .toggle.opened:before { 45 | content: '▼'; 46 | } 47 | 48 | table.test-summary tr.test-interface .toggle.closed:before { 49 | content: '▶'; 50 | } 51 | 52 | table.test-summary .test-target > span { 53 | display: block; 54 | margin-top: 4px; 55 | } 56 | table.test-summary .test-target > span:first-child { 57 | margin-top: 0; 58 | } 59 | -------------------------------------------------------------------------------- /docs/file/src/Parser.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/Parser.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/Parser.js

37 |
/**
 38 |  * Parser - Receives any strategy and safely implement it
 39 |  *
 40 |  * @constructor
 41 |  * @param {Base} strategy - Any strategy implementing the Base's prototype
 42 |  */
 43 | function Parser (strategy) {
 44 |   this.strategy = strategy
 45 | }
 46 | 
 47 | /**
 48 |  * Parser.prototype.parse - Exposes the parsing from string to any valid JS type with the strategy
 49 |  *
 50 |  * @param {string} data
 51 |  * @param {object} options
 52 |  */
 53 | Parser.prototype.parse = function parse (data, options) {
 54 |   return this.strategy.parse(data, options)
 55 | }
 56 | 
 57 | /**
 58 |  * Parser.prototype.stringify - Exposes the stringify() method from any valid JS type to expected type with the strategy
 59 |  *
 60 |  * @param {*} data
 61 |  * @param {object} options
 62 |  */
 63 | Parser.prototype.stringify = function stringify (data, options) {
 64 |   return this.strategy.stringify(data, options)
 65 | }
 66 | 
 67 | /**
 68 |  * Parser.prototype.valid - Exposes the valid() method from strategy. Checks if given data is valid
 69 |  *
 70 |  * @param {string} data
 71 |  * @param {object} options
 72 |  */
 73 | Parser.prototype.valid = function stringify (data, options) {
 74 |   return this.strategy.valid(data, options)
 75 | }
 76 | 
 77 | /**
 78 |  * Parser.prototype.pipeStringify - Exposes the pipeStringify() method from strategy. Streams data through stringify
 79 |  */
 80 | Parser.prototype.pipeStringify = function pipeStringify () {
 81 |   return this.strategy.pipeStringify()
 82 | }
 83 | 
 84 | /**
 85 |  * Parser.prototype.pipeParse - Exposes the pipeParse() method from strategy. Streams data through parse
 86 |  */
 87 | Parser.prototype.pipeParse = function pipeParse () {
 88 |   return this.strategy.pipeParse()
 89 | }
 90 | 
 91 | Parser.prototype.get = function get (data, path) {}
 92 | 
 93 | Parser.prototype.has = function has (data, path) {}
 94 | 
 95 | module.exports = Parser
 96 | 
97 | 98 |
99 | 100 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/file/src/errors/NotImplemented.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/errors/NotImplemented.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/errors/NotImplemented.js

37 |
/**
38 |  * NotImplemented
39 |  */
40 | function NotImplemented () {
41 |   this.name = 'NotImplemented'
42 |   this.message = 'This method haven\'t been implemented yet'
43 | }
44 | 
45 | NotImplemented.prototype = new Error()
46 | 
47 | module.exports = NotImplemented
48 | 
49 | 50 |
51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/file/src/errors/ParserError.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/errors/ParserError.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/errors/ParserError.js

37 |
/**
38 |  * ParseError
39 |  *
40 |  * @param {string} format - which format the error ocurred
41 |  * @param {object} context - any context info for debugging
42 |  */
43 | function ParseError (format, context = {}) {
44 |   this.name = 'ParseError'
45 |   this.message = `Failed to parse ${format}`
46 |   this.context = context
47 | }
48 | 
49 | ParseError.prototype = new Error()
50 | 
51 | module.exports = ParseError
52 | 
53 | 54 |
55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /docs/file/src/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/index.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/index.js

37 |
const {
38 |   Json,
39 |   Xml,
40 |   Yaml,
41 |   Csv
42 | } = require('./strategies')
43 | const Parser = require('./Parser')
44 | 
45 | module.exports = {
46 |   json: new Parser(new Json()),
47 |   xml: new Parser(new Xml()),
48 |   yaml: new Parser(new Yaml()),
49 |   csv: new Parser(new Csv())
50 | }
51 | 
52 | 53 |
54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Base.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Base.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Base.js

37 |
const NotImplemented = require('../errors/NotImplemented')
 38 | const ParserError = require('../errors/ParserError')
 39 | 
 40 | /**
 41 |  * Base class for strategies around the Parser
 42 |  * Every function that haven't been implemented
 43 |  * will throw an NotImplementedError
 44 |  *
 45 |  * @constructor
 46 |  */
 47 | function Base () {}
 48 | 
 49 | /**
 50 |  * Base.prototype.stringify - receives * form of data and turns it into a string
 51 |  *
 52 |  * @param {*} data
 53 |  * @param {object} options
 54 |  * @throws {NotImplemented} This method must be implemented
 55 |  */
 56 | Base.prototype.stringify = function stringify (data, options) {
 57 |   throw new NotImplemented()
 58 | }
 59 | 
 60 | /**
 61 |  * Base.prototype.parse - receives an string and translate it to valid JavaScript
 62 |  *
 63 |  * @param {string} data
 64 |  * @param {object} options
 65 |  * @throws {NotImplemented} This method must be implemented
 66 |  */
 67 | Base.prototype.parse = function parse (data, options) {
 68 |   throw new NotImplemented()
 69 | }
 70 | 
 71 | /**
 72 |  * Base.prototype.pipe - prototype for streams
 73 |  *
 74 |  * @throws {NotImplemented} This method must be implemented
 75 |  */
 76 | Base.prototype.pipe = function pipe () {
 77 |   throw new NotImplemented()
 78 | }
 79 | 
 80 | /**
 81 |  * Base.prototype.valid - checks if a given value is valid in desired format using the implemented method parse(), ignoring any exception
 82 |  *
 83 |  * @param {object} options - any option accepted for the implemented method parse()
 84 |  * @returns {boolean} wether or not the given data is a valid format
 85 |  */
 86 | Base.prototype.valid = function valid (data, options = {}) {
 87 |   try {
 88 |     this.parse(data, options)
 89 |     return true
 90 |   } catch (error) {
 91 |     if (error instanceof ParserError) {
 92 |       return false
 93 |     }
 94 | 
 95 |     throw error
 96 |   }
 97 | }
 98 | 
 99 | /**
100 |  * Base.prototype.pipeParse - prototype for streams
101 |  *
102 |  * @throws {NotImplemented} This method must be implemented
103 |  */
104 | Base.prototype.pipeParse = function pipeParse () {
105 |   throw new NotImplemented()
106 | }
107 | 
108 | /**
109 |  * Base.prototype.pipeStringify - prototype for streams
110 |  *
111 |  * @throws {NotImplemented} This method must be implemented
112 |  */
113 | Base.prototype.pipeStringify = function pipeStringify () {
114 |   throw new NotImplemented()
115 | }
116 | 
117 | module.exports = Base
118 | 
119 | 120 |
121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Csv.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Csv.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Csv.js

37 |
const Base = require('./Base')
 38 | const ParserError = require('../errors/ParserError')
 39 | const csvParser = require('csv-parse/lib/sync')
 40 | const csvStringify = require('csv-stringify/lib/sync')
 41 | const csvParserStream = require('csv-parse')
 42 | const csvStringifyStream = require('csv-stringify')
 43 | 
 44 | /**
 45 |  * Csv - Support for CSV filetype
 46 |  *
 47 |  * @constructor
 48 |  */
 49 | function Csv () {}
 50 | 
 51 | Csv.prototype = Object.create(Base.prototype)
 52 | 
 53 | /**
 54 |  * Csv.prototype.parse - receives an CSV string and returns valid JS
 55 |  *
 56 |  * @param {string} data
 57 |  * @param {object} [options]
 58 |  * @param {(boolean|array|function)} [options.headers] - If should parse first line as the headers, default is true
 59 |  * @param {(string|Buffer)} [options.delimiter] - Which delimiters to use when parsing, defaults to comma `,`
 60 |  * @param {number} [options.skipLines] - How many lines it should skip before parsing, defaults to 1
 61 |  * @param {number} [options.offset] - How many lines it should parse, defaults to -1
 62 |  * @returns {array}
 63 |  */
 64 | Csv.prototype.parse = function parse (data, options = {}) {
 65 |   const config = {
 66 |     columns: true,
 67 |     skip_empty_lines: true,
 68 |     delimiter: options.delimiter || ',',
 69 |     from_line: options.skipLines || 1
 70 |   }
 71 | 
 72 |   if (Object.prototype.hasOwnProperty.apply(options, ['headers'])) {
 73 |     config.columns = options.headers
 74 |   }
 75 | 
 76 |   if (options.offset) {
 77 |     config.to_line = options.offset
 78 |   }
 79 | 
 80 |   try {
 81 |     return csvParser(data, config)
 82 |   } catch (e) {
 83 |     const context = {
 84 |       code: e.code,
 85 |       message: e.message,
 86 |       column: e.column,
 87 |       emptyLines: e.empty_lines,
 88 |       header: e.header,
 89 |       index: e.index,
 90 |       lines: e.lines,
 91 |       quoting: e.quoting,
 92 |       records: e.records
 93 |     }
 94 | 
 95 |     throw new ParserError('csv', context)
 96 |   }
 97 | }
 98 | 
 99 | /**
100 |  * Csv.prototype.stringify - receives * valid JS data and returns it as CSV
101 |  *
102 |  * @param {array} data
103 |  * @param {object} [options]
104 |  * @param {boolean} [options.headers] - If should set first line as the headers, default is true
105 |  * @param {(array|object)} [options.columns] - Custom column mapping, see examples for more
106 |  * @returns {string}
107 |  */
108 | Csv.prototype.stringify = function stringify (data, options = {}) {
109 |   const config = {
110 |     header: true
111 |   }
112 | 
113 |   if (options.headers === false) {
114 |     config.header = false
115 |   }
116 | 
117 |   if (options.columns) {
118 |     config.columns = options.columns
119 |   }
120 | 
121 |   return csvStringify(data, config)
122 | }
123 | 
124 | /**
125 |  * Csv.prototype.pipeParse - allow streaming data
126 |  *
127 |  * @param {object} [options]
128 |  * @param {(boolean|array|function)} [options.headers] - If should parse first line as the headers, default is true
129 |  * @param {string} [options.delimiter] - Which delimiters to use when parsing, defaults to comma `,`
130 |  */
131 | Csv.prototype.pipeParse = function pipeParse (options = {}) {
132 |   const config = {
133 |     delimiter: options.delimiter || ',',
134 |     columns: Reflect.has(options, 'headers') ? options.headers : true
135 |   }
136 | 
137 |   return csvParserStream(config)
138 | }
139 | 
140 | /**
141 |  * Csv.prototype.pipeStringify - stream
142 |  *
143 |  * @param {array} data
144 |  * @param {object} [options]
145 |  * @param {boolean} [options.headers] - If should set first line as the headers, default is true
146 |  * @param {string} [options.delimiter] - Which delimiters to use when parsing, defaults to comma `,`
147 |  * @param {(array|object)} [options.columns] - Custom column mapping, see examples for more
148 |  */
149 | Csv.prototype.pipeStringify = function pipeStringify (options = {}) {
150 |   const config = {
151 |     delimiter: options.delimiter || ',',
152 |     header: Reflect.has(options, 'headers') ? !!options.headers : true
153 |   }
154 | 
155 |   if (Reflect.has(options, 'columns')) {
156 |     config.columns = options.columns
157 |   }
158 | 
159 |   return csvStringifyStream(config)
160 | }
161 | 
162 | module.exports = Csv
163 | 
164 | 165 |
166 | 167 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Json.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Json.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Json.js

37 |
const Base = require('./Base')
 38 | const ParserError = require('../errors/ParserError')
 39 | const JSONStream = require('JSONStream')
 40 | 
 41 | /**
 42 |  * Json - Support for JSON filetype
 43 |  *
 44 |  * @constructor
 45 |  */
 46 | function Json () {}
 47 | 
 48 | Json.prototype = Object.create(Base.prototype)
 49 | 
 50 | /**
 51 |  * Json.prototype.parse - receives an JSON string and returns valid JS
 52 |  *
 53 |  * @param {string} data
 54 |  * @throws {ParserError} In case the JSON string is not valid, ParserError will be thrown
 55 |  * @returns {*}
 56 |  */
 57 | Json.prototype.parse = function parse (data) {
 58 |   try {
 59 |     return JSON.parse(data)
 60 |   } catch (e) {
 61 |     throw new ParserError('json')
 62 |   }
 63 | }
 64 | 
 65 | /**
 66 |  * Json.prototype.stringify - receives * valid JS data and returns it as JSON
 67 |  *
 68 |  * @param {*} data
 69 |  * @returns {string}
 70 |  */
 71 | Json.prototype.stringify = function stringify (data) {
 72 |   return JSON.stringify(data)
 73 | }
 74 | 
 75 | /**
 76 |  * Json.prototype.pipeStringify - helps to stream object or array into JSON valid data
 77 |  *
 78 |  * @param {object} [config] - sets config for stream
 79 |  * @param {string} [config.type='array'] - which type of data you're streaming, defaults do array
 80 |  * @returns {WritableStream}
 81 |  */
 82 | Json.prototype.pipeStringify = function pipeStringify (config = {}) {
 83 |   config.type = config.type || 'array'
 84 |   const streams = {
 85 |     object: JSONStream.stringifyObject,
 86 |     array: JSONStream.stringify
 87 |   }
 88 | 
 89 |   const fn = streams[config.type]
 90 | 
 91 |   if (!fn) {
 92 |     throw new ParserError(`Supplied type "${config.type}" is not allowed. Use either "array" or "object"`)
 93 |   }
 94 | 
 95 |   return fn()
 96 | }
 97 | 
 98 | /**
 99 |  * Json.prototype.pipeStringify - helps to stream JSON data to JS
100 |  *
101 |  * @param {object} [config] - sets config for stream
102 |  * @param {string} [config.path] - select which data path to be parsed from JSON to JS
103 |  * @returns {Stream}
104 |  */
105 | Json.prototype.pipeParse = function pipeParse (config) {
106 |   return JSONStream.parse()
107 | }
108 | 
109 | module.exports = Json
110 | 
111 | 112 |
113 | 114 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Xml.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Xml.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Xml.js

37 |
const Base = require('./Base')
 38 | const ParserError = require('../errors/ParserError')
 39 | const xml = require('xml-js')
 40 | 
 41 | /**
 42 |  * Xml - Support for XML filetype
 43 |  *
 44 |  * @constructor
 45 |  */
 46 | function Xml () {
 47 |   this.XML_VERSION_TAG = {
 48 |     _declaration: {
 49 |       _attributes: {
 50 |         version: '1.0',
 51 |         encoding: 'utf-8'
 52 |       }
 53 |     }
 54 |   }
 55 | }
 56 | 
 57 | Xml.prototype = Object.create(Base.prototype)
 58 | 
 59 | /**
 60 |  * Xml.prototype.setXmlDeclaration - sets XML declaration tag on first position of array or object
 61 |  *
 62 |  * @param {(object|array)} data - input data
 63 |  * @returns {(object|array)}
 64 |  */
 65 | Xml.prototype.setXmlDeclaration = function setXmlDeclaration (data) {
 66 |   if (Array.isArray(data)) {
 67 |     data = [this.XML_VERSION_TAG, ...data]
 68 |   } else {
 69 |     data = { ...this.XML_VERSION_TAG, ...data }
 70 |   }
 71 | 
 72 |   return data
 73 | }
 74 | 
 75 | /**
 76 |  * Xml.prototype.stringify - receives * valid JS data and returns it as XML
 77 |  *
 78 |  * @param {(object|array)} data
 79 |  * @param {Object} options - options for turning JS data into XML
 80 |  * @param {boolean} options.ignoreDeclaration - don't output XML version tag, default is true
 81 |  * @example
 82 |  * // returns '<?xml version="1.0" encoding="utf-8"?><package>parser</package>'
 83 |  * const data = { package: 'parser' }
 84 |  * Xml().stringify(data)
 85 |  * @example
 86 |  * // returns '<package>parser</package>'
 87 |  * const data = { package: 'parser' }
 88 |  * Xml().stringify(data, { ignoreDeclaration: true })
 89 |  * @returns {string}
 90 |  */
 91 | Xml.prototype.stringify = function stringify (data, options = {}) {
 92 |   const config = {
 93 |     compact: true,
 94 |     ignoreDeclaration: false
 95 |   }
 96 | 
 97 |   data = this.setXmlDeclaration(data)
 98 | 
 99 |   if (options.ignoreDeclaration) {
100 |     config.ignoreDeclaration = true
101 |   }
102 | 
103 |   return xml.js2xml(data, config)
104 | }
105 | 
106 | /**
107 |  * Xml.prototype.parse - receives an XML string and translate it to valid JavaScript
108 |  *
109 |  * @param {string} data
110 |  * @param {object} options
111 |  * @param {object} options.showDeclaration - force parsing XML declaration tag
112 |  * @throws {NotImplemented} This method must be implemented
113 |  */
114 | Xml.prototype.parse = function parse (data, options = {}) {
115 |   try {
116 |     const config = {
117 |       compact: true,
118 |       ignoreDeclaration: true,
119 |       nativeType: true,
120 |       nativeTypeAttributes: true
121 |     }
122 | 
123 |     if (options.showDeclaration) {
124 |       config.ignoreDeclaration = false
125 |     }
126 | 
127 |     return xml.xml2js(data, config)
128 |   } catch (error) {
129 |     throw new ParserError(error.message)
130 |   }
131 | }
132 | 
133 | module.exports = Xml
134 | 
135 | 136 |
137 | 138 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Xml/XmlTag.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Xml/XmlTag.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Xml/XmlTag.js

37 |
function XmlDeclaration (version, encoding) {
38 |   this.name = 'declaration'
39 |   this.version = version
40 |   this.encoding = encoding
41 | }
42 | 
43 | function XmlTag (name, value, attributes, tags) {
44 |   this.name = name
45 |   this.value = value
46 |   this.attributes = attributes
47 |   this.tags = tags
48 | }
49 | 
50 | XmlTag.prototype.reset = function reset () {
51 |   return new XmlTag(this.name, this.value, this.attributes, this.tags)
52 | }
53 | 
54 | function XmlCharacterData (cdata) {
55 |   this.name = 'cdata'
56 |   this.cdata = cdata
57 | }
58 | 
59 | module.exports = { XmlTag, XmlDeclaration, XmlCharacterData }
60 | 
61 | 62 |
63 | 64 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Xml/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Xml/index.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Xml/index.js

37 |
const Base = require('../Base')
 38 | const ParserError = require('../../errors/ParserError')
 39 | const xml = require('xml-js')
 40 | const NotImplemented = require('../../errors/NotImplemented')
 41 | const { Transform } = require('stream')
 42 | const StreamParser = require('node-xml-stream')
 43 | const { XmlTag, XmlCharacterData, XmlDeclaration } = require('./XmlTag')
 44 | 
 45 | /**
 46 |  * Xml - Support for XML filetype
 47 |  *
 48 |  * @constructor
 49 |  */
 50 | function Xml () {
 51 |   this.XML_VERSION_TAG = {
 52 |     _declaration: {
 53 |       _attributes: {
 54 |         version: '1.0',
 55 |         encoding: 'utf-8'
 56 |       }
 57 |     }
 58 |   }
 59 | 
 60 |   this.XML_JS_KEYS = {
 61 |     declarationKey: '_declaration',
 62 |     instructionKey: '_instruction',
 63 |     attributesKey: '_attributes',
 64 |     textKey: '_text',
 65 |     cdataKey: '_cdata',
 66 |     doctypeKey: '_doctype',
 67 |     commentKey: '_comment',
 68 |     parentKey: '_parent',
 69 |     typeKey: '_type',
 70 |     nameKey: '_name',
 71 |     elementsKey: '_elements'
 72 |   }
 73 | }
 74 | 
 75 | Xml.prototype = Object.create(Base.prototype)
 76 | 
 77 | /**
 78 |  * Xml.prototype.setXmlDeclaration - sets XML declaration tag on first position of array or object
 79 |  *
 80 |  * @param {(object|array)} data - input data
 81 |  * @returns {(object|array)}
 82 |  */
 83 | Xml.prototype.setXmlDeclaration = function setXmlDeclaration (data) {
 84 |   if (Array.isArray(data)) {
 85 |     data = [this.XML_VERSION_TAG, ...data]
 86 |   } else {
 87 |     data = { ...this.XML_VERSION_TAG, ...data }
 88 |   }
 89 | 
 90 |   return data
 91 | }
 92 | 
 93 | /**
 94 |  * Xml.prototype.stringify - receives * valid JS data and returns it as XML
 95 |  *
 96 |  * @param {(object|array)} data
 97 |  * @param {Object} [options] - options for turning JS data into XML
 98 |  * @param {boolean} [options.ignoreDeclaration] - don't output XML version tag, default is true
 99 |  * @returns {string}
100 |  */
101 | Xml.prototype.stringify = function stringify (data, options = {}) {
102 |   const config = {
103 |     compact: true,
104 |     ignoreDeclaration: false
105 |   }
106 | 
107 |   data = this.setXmlDeclaration(data)
108 | 
109 |   if (options.ignoreDeclaration) {
110 |     config.ignoreDeclaration = true
111 |   }
112 | 
113 |   return xml.js2xml(data, config)
114 | }
115 | 
116 | /**
117 |  * Xml.prototype.parse - receives an XML string and translate it to valid JavaScript
118 |  *
119 |  * @param {string} data
120 |  * @param {object} [options]
121 |  * @param {boolean} [options.showDeclaration] - force parsing XML declaration tag
122 |  * @param {boolean} [options.verbose] - makes xml2js return non compact mode, defaults to false
123 |  * @param {boolean} [options.experimentalXmlTag] - use experimental XmlTag prototype, default is false
124 |  * @throws {NotImplemented} This method must be implemented
125 |  */
126 | Xml.prototype.parse = function parse (data, options = {}) {
127 |   try {
128 |     const config = {
129 |       compact: true,
130 |       ignoreDeclaration: true,
131 |       nativeType: true,
132 |       nativeTypeAttributes: true
133 |     }
134 | 
135 |     if (options.showDeclaration) {
136 |       config.ignoreDeclaration = false
137 |     }
138 | 
139 |     if (options.verbose) {
140 |       config.compact = false
141 |     }
142 | 
143 |     const result = xml.xml2js(data, config)
144 | 
145 |     if (options.experimentalXmlTag) {
146 |       return this.toXmlTag(result)
147 |     }
148 | 
149 |     return result
150 |   } catch (error) {
151 |     throw new ParserError(error.message)
152 |   }
153 | }
154 | 
155 | /**
156 |  * Xml.prototype.toXmlTag - turns xml2js non-compact result into XmlTag and XmlResult
157 |  *
158 |  * @param {object} xml2jsResult
159 |  * @throws {NotImplemented}
160 |  */
161 | Xml.prototype.toXmlTag = function toXmlTag (xml2jsResult) {
162 |   throw new NotImplemented()
163 | }
164 | 
165 | /**
166 |  * Xml.prototype.pipeParse - stream
167 |  *
168 |  * @param {object} [options]
169 |  * @param {Number} [options.depth=0]
170 |  */
171 | Xml.prototype.pipeParse = function pipeParse (options = {}) {
172 |   options.depth = options.depth || 0
173 |   const parser = new StreamParser()
174 | 
175 |   let index = 0
176 |   let parsedTags = new Map()
177 |   const toEmit = []
178 |   const lastTag = {
179 |     index: null,
180 |     name: null,
181 |     tagIndex: null
182 |   }
183 | 
184 |   const getFirstTagName = map => {
185 |     if (map.has(0) === false) {
186 |       return null
187 |     }
188 | 
189 |     const mapPosZero = map.get(0)
190 |     const arrayMap = Array.from(mapPosZero)
191 | 
192 |     if (arrayMap.length === 0) {
193 |       return null
194 |     }
195 | 
196 |     const keyValue = arrayMap[0]
197 | 
198 |     if (keyValue.length === 0) {
199 |       return null
200 |     }
201 | 
202 |     return keyValue[0]
203 |   }
204 | 
205 |   parser.on('opentag', (name, attrs) => {
206 |     const inheritFrom = {
207 |       index: null,
208 |       name: null
209 |     }
210 | 
211 |     if (index >= 1) {
212 |       const beforeIndex = index - 1
213 |       const beforeKey = [
214 |         ...parsedTags
215 |           .get(beforeIndex)
216 |           .keys()
217 |       ].reverse()[0]
218 |       inheritFrom.index = beforeIndex
219 |       inheritFrom.name = beforeKey
220 |     }
221 | 
222 |     if (!parsedTags.has(index)) {
223 |       parsedTags.set(index, new Map())
224 |     }
225 | 
226 |     if (!parsedTags.get(index).has(name)) {
227 |       parsedTags.get(index).set(name, [])
228 |     }
229 | 
230 |     const tag = new XmlTag(name, null, attrs, [])
231 |     tag.inheritFrom = inheritFrom
232 | 
233 |     lastTag.index = index
234 |     lastTag.name = name
235 |     lastTag.tagIndex = parsedTags.get(index).get(name).push(tag) - 1
236 |     tag.inheritFrom.tagIndex = lastTag.tagIndex
237 |     index = index + 1
238 |   })
239 | 
240 |   parser.on('text', (text) => {
241 |     parsedTags
242 |       .get(lastTag.index)
243 |       .get(lastTag.name)[lastTag.tagIndex]
244 |       .value = text
245 | 
246 |     lastTag.index = null
247 |     lastTag.name = null
248 |     lastTag.tagIndex = null
249 |   })
250 | 
251 |   parser.on('closetag', (name) => {
252 |     index = index - 1
253 | 
254 |     if (index === options.depth) {
255 |       /**
256 |        * must reorganize data to a single object
257 |        * them emit it
258 |       */
259 |       let entries = Array.from(parsedTags).reverse()
260 |       entries = entries.map(
261 |         ([intIndex, tagsMap]) => ({
262 |           intIndex, tagsMap: Array.from(tagsMap).reverse()
263 |         })
264 |       )
265 |       entries.pop()
266 |       entries.forEach(entry => {
267 |         const intIndex = entry.intIndex === 0 ? entry.intIndex : entry.intIndex - 1
268 |         const indexedTags = parsedTags.get(intIndex)
269 | 
270 |         entry.tagsMap.forEach(tag => {
271 |           const list = tag[1]
272 |           list.forEach(tagToBePushed => {
273 |             indexedTags
274 |               .get(tagToBePushed.inheritFrom.name)[0]
275 |               .tags
276 |               .push(tagToBePushed.reset())
277 |           })
278 |         })
279 |       })
280 | 
281 |       parsedTags
282 |         .get(index)
283 |         .get(name)
284 |         .forEach(tag => toEmit.push(tag.reset()))
285 |     }
286 | 
287 |     if (name === getFirstTagName(parsedTags)) {
288 |       parsedTags = new Map()
289 |     }
290 |   })
291 | 
292 |   parser.on('cdata', cdata => {
293 |     const CData = new XmlCharacterData(cdata)
294 |     toEmit.push(CData)
295 |   })
296 | 
297 |   parser.on('instruction', (name, attrs) => {
298 |     const declaration = new XmlDeclaration(attrs.version, attrs.encoding)
299 |     toEmit.push(declaration)
300 |   })
301 | 
302 |   return new Transform({
303 |     objectMode: true,
304 |     transform (chunk, encoding, ack) {
305 |       parser.write(chunk.toString())
306 | 
307 |       if (toEmit.length > 0) {
308 |         this.push(toEmit.shift())
309 |       }
310 | 
311 |       ack()
312 |     }
313 |   })
314 | }
315 | 
316 | /**
317 |  * Xml.prototype.pipeStringify - stream from JS data into XML
318 |  *
319 |  * @param {object} [options] - all options to stringify
320 |  * @param {object} [options.mainTag] - the wrapping tag
321 |  * @param {string} [options.mainTag.name] - the wrapping tag's name
322 |  */
323 | Xml.prototype.pipeStringify = function pipeStringify (options = {}) {
324 |   options.mainTag = options.mainTag || {}
325 |   const defaultContent = 'FAKE_CONTENT'
326 |   const name = options.mainTag.name
327 |   const contents = options.mainTag.text || defaultContent
328 |   const tag = { [name || 'fake']: contents }
329 |   const stringified = this.stringify(tag)
330 | 
331 |   const lastIndexOfArrow = stringified.lastIndexOf('<')
332 |   let initialTag = stringified.substr(0, lastIndexOfArrow)
333 | 
334 |   if (initialTag.indexOf(defaultContent) !== -1) {
335 |     initialTag.replace(defaultContent, '')
336 |   }
337 | 
338 |   let endingTag = stringified.substr(
339 |     lastIndexOfArrow,
340 |     stringified.length
341 |   )
342 | 
343 |   if (Reflect.has(options.mainTag, 'name') === false) {
344 |     const firstArrowIndex = initialTag.indexOf('>') + 1
345 |     initialTag = initialTag.substr(0, firstArrowIndex)
346 |     endingTag = ''
347 |   }
348 | 
349 |   const xml = this
350 |   let isFirstData = true
351 | 
352 |   return new Transform({
353 |     objectMode: true,
354 |     transform (chunk, encoding, ack) {
355 |       const options = {
356 |         ignoreDeclaration: true
357 |       }
358 | 
359 |       if (isFirstData) {
360 |         this.push(initialTag)
361 |         isFirstData = false
362 |       }
363 | 
364 |       const toBePushed = xml.stringify(chunk, options)
365 |       this.push(toBePushed)
366 | 
367 |       ack()
368 |     },
369 |     flush (cb) {
370 |       this.push(endingTag)
371 |       cb()
372 |     }
373 |   })
374 | }
375 | 
376 | module.exports = Xml
377 | 
378 | 379 |
380 | 381 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | -------------------------------------------------------------------------------- /docs/file/src/strategies/Yaml.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/Yaml.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/Yaml.js

37 |
const Base = require('./Base')
 38 | const ParserError = require('../errors/ParserError')
 39 | const yaml = require('js-yaml')
 40 | 
 41 | /**
 42 |  * Yaml - Support for YAML filetype
 43 |  *
 44 |  * @constructor
 45 |  */
 46 | function Yaml () {
 47 | }
 48 | 
 49 | Yaml.prototype = Object.create(Base.prototype)
 50 | 
 51 | /**
 52 |  * Yaml.prototype.stringify - receives * valid JS data and returns it as YAML
 53 |  *
 54 |  * @param {object} data
 55 |  * @param {Object} options - options for turning JS data into YAML
 56 |  * @throws {ParserError} For invalid data type
 57 |  * @returns {string}
 58 |  */
 59 | Yaml.prototype.stringify = function stringify (data, options = {}) {
 60 |   if (Array.isArray(data)) {
 61 |     throw new ParserError('Only plain objects are accepted for stringify()')
 62 |   }
 63 | 
 64 |   return yaml.safeDump(data)
 65 | }
 66 | 
 67 | /**
 68 |  * Yaml.prototype.parse - receives an YAML string and translate it to valid JavaScript
 69 |  *
 70 |  * @param {string} data
 71 |  * @param {object} options
 72 |  * @returns {object} Parsed YAML data as JS object
 73 |  */
 74 | Yaml.prototype.parse = function parse (data, options = {}) {
 75 |   try {
 76 |     return yaml.safeLoad(data)
 77 |   } catch (e) {
 78 |     const context = {
 79 |       errorName: e.name,
 80 |       message: e.message,
 81 |       mark: e.mark
 82 |     }
 83 | 
 84 |     throw new ParserError('yaml', context)
 85 |   }
 86 | }
 87 | 
 88 | module.exports = Yaml
 89 | 
90 | 91 |
92 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/file/src/strategies/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/strategies/index.js | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

src/strategies/index.js

37 |
const Json = require('./Json')
38 | const Xml = require('./Xml')
39 | const Yaml = require('./Yaml')
40 | const Csv = require('./Csv')
41 | 
42 | module.exports = { Json, Xml, Yaml, Csv }
43 | 
44 | 45 |
46 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/identifiers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Reference | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 35 | 36 |

References

37 | 38 |
39 |
40 | 41 |
42 | 43 | 44 |
45 |
46 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/image/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | @ratio@ 15 | @ratio@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onhernandes/parserblade/8c8b2bf1fc1baa16c953e121ad7029bf936c8686/docs/image/esdoc-logo-mini-black.png -------------------------------------------------------------------------------- /docs/image/esdoc-logo-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onhernandes/parserblade/8c8b2bf1fc1baa16c953e121ad7029bf936c8686/docs/image/esdoc-logo-mini.png -------------------------------------------------------------------------------- /docs/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onhernandes/parserblade/8c8b2bf1fc1baa16c953e121ad7029bf936c8686/docs/image/github.png -------------------------------------------------------------------------------- /docs/image/manual-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | manual 13 | manual 14 | @value@ 15 | @value@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onhernandes/parserblade/8c8b2bf1fc1baa16c953e121ad7029bf936c8686/docs/image/search.png -------------------------------------------------------------------------------- /docs/inject/css/0-parser.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onhernandes/parserblade/8c8b2bf1fc1baa16c953e121ad7029bf936c8686/docs/inject/css/0-parser.css -------------------------------------------------------------------------------- /docs/lint.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "valid", 4 | "filePath": "src/strategies/Base.js", 5 | "lines": [ 6 | { 7 | "lineNumber": 49, 8 | "line": "Base.prototype.valid = function valid (data, options = {}) {" 9 | } 10 | ], 11 | "codeParams": [ 12 | "data", 13 | "options" 14 | ], 15 | "docParams": [ 16 | "options" 17 | ] 18 | }, 19 | { 20 | "name": "pipeStringify", 21 | "filePath": "src/strategies/Csv.js", 22 | "lines": [ 23 | { 24 | "lineNumber": 112, 25 | "line": "Csv.prototype.pipeStringify = function pipeStringify (options = {}) {" 26 | } 27 | ], 28 | "codeParams": [ 29 | "options" 30 | ], 31 | "docParams": [ 32 | "data", 33 | "options" 34 | ] 35 | } 36 | ] -------------------------------------------------------------------------------- /docs/manual/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manual | parser 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 45 | 46 |

parser

A all-in-one parser for Javascript, heavily inspired by: https://github.com/nathanmac/Parser. It allows you to work with JSON, XML, CSV and YAML more without worrying about which module install. It's designed to work just as JSON.parse and JSON.stringify does, with some improvements.

47 |

Installation

$ npm install --save parser
 48 | 
49 |

Usage

Every filetype has two main methods: stringify() and parse(), both receives two arguments, data containing any type of data and an options object.

50 |

CSV

const { csv } = require('parser')
 51 | 
 52 | // Parsing
 53 | const input = 'title,platform\nStardew Valley,Steam'
 54 | const result = csv.parse(input)
 55 | console.log(result) // [ { title: 'Stardew Valley', platform: 'Steam' } ]
 56 | 
 57 | // Stringifying
 58 | console.log(
 59 |   csv.stringify(result)
 60 | ) // 'title,platform\nStardew Valley,Steam'
 61 | 
62 |

YAML

const { yaml } = require('parser')
 63 | 
 64 | // Parsing
 65 | const input = 'title: Stardew Valley\nplatform: Steam'
 66 | const result = yaml.parse(input)
 67 | console.log(result) // { title: 'Stardew Valley', platform: 'Steam' }
 68 | 
 69 | // Stringifying
 70 | console.log(
 71 |   yaml.stringify(result)
 72 | ) // 'title: Stardew Valley\nplatform: Steam'
 73 | 
74 |

XML

const { xml } = require('parser')
 75 | 
 76 | // Parsing
 77 | const input = '<?xml version="1.0" encoding="utf-8"?><package>lodash</package>'
 78 | const result = xml.parse(input)
 79 | console.log(result) // { package: 'lodash' }
 80 | 
 81 | // Stringifying
 82 | console.log(
 83 |   xml.stringify(result)
 84 | ) // '<?xml version="1.0" encoding="utf-8"?><package>lodash</package>'
 85 | 
86 |

License

MIT ©

87 |
88 |
89 | 90 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/manual/yaml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manual | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 98 | 99 |

YAML

Usage

Both yaml.parse() and yaml.stringify() accepts the data to be parsed/stringified as the first argument, and an option's object as the second.

100 |

Parse

const assert = require('assert'')
101 | const { yaml } = require('parserblade')
102 | const input = 'series: Bleach\nseasons: 16'
103 | const result = yaml.parse(data)
104 | assert.deepStrictEqual(
105 |   result,
106 |   { series: 'Bleach', seasons: 16 }
107 | )
108 | 
109 |

Stringify

const assert = require('assert'')
110 | const { yaml } = require('parserblade')
111 | const input = { series: 'Bleach', seasons: 16 }
112 | const result = yaml.parse(data)
113 | assert.equal(
114 |   result,
115 |   'series: Bleach\nseasons: 16'
116 | )
117 | 
118 |

Valid

Just checks if given string is a valid YAML

119 |
const assert = require('assert')
120 | const { yaml } = require('parserblade')
121 | const result = yaml.valid('[name:\nStardew')
122 | 
123 | assert.equal(
124 |   result,
125 |   false
126 | )
127 | 
128 |
129 |
130 | 131 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /docs/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | var matched = location.hash.match(/errorLines=([\d,]+)/); 5 | if (matched) return; 6 | 7 | function adjust() { 8 | window.scrollBy(0, -55); 9 | var el = document.querySelector('.inner-link-active'); 10 | if (el) el.classList.remove('inner-link-active'); 11 | 12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 14 | var el = document.querySelector(id); 15 | if (el) el.classList.add('inner-link-active'); 16 | } 17 | 18 | window.addEventListener('hashchange', adjust); 19 | 20 | if (location.hash) { 21 | setTimeout(adjust, 0); 22 | } 23 | })(); 24 | 25 | (function(){ 26 | var els = document.querySelectorAll('[href^="#"]'); 27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash 28 | for (var i = 0; i < els.length; i++) { 29 | var el = els[i]; 30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /docs/script/manual.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/); 3 | if (!matched) return; 4 | 5 | var currentName = matched[1]; 6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]'; 7 | var styleText = cssClass + '{ display: block; }\n'; 8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }'; 9 | var style = document.createElement('style'); 10 | style.textContent = styleText; 11 | document.querySelector('head').appendChild(style); 12 | })(); 13 | -------------------------------------------------------------------------------- /docs/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /docs/script/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/script/pretty-print.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | prettyPrint(); 3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]'); 4 | for (var i = 0; i < lines.length; i++) { 5 | lines[i].id = 'lineNumber' + (i + 1); 6 | } 7 | 8 | var matched = location.hash.match(/errorLines=([\d,]+)/); 9 | if (matched) { 10 | var lines = matched[1].split(','); 11 | for (var i = 0; i < lines.length; i++) { 12 | var id = '#lineNumber' + lines[i]; 13 | var el = document.querySelector(id); 14 | el.classList.add('error-line'); 15 | } 16 | return; 17 | } 18 | 19 | if (location.hash) { 20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 22 | var line = document.querySelector(id); 23 | if (line) line.classList.add('active'); 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/script/search.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var searchIndex = window.esdocSearchIndex; 3 | var searchBox = document.querySelector('.search-box'); 4 | var input = document.querySelector('.search-input'); 5 | var result = document.querySelector('.search-result'); 6 | var selectedIndex = -1; 7 | var prevText; 8 | 9 | // active search box and focus when mouse enter on search box. 10 | searchBox.addEventListener('mouseenter', function(){ 11 | searchBox.classList.add('active'); 12 | input.focus(); 13 | }); 14 | 15 | // search with text when key is upped. 16 | input.addEventListener('keyup', function(ev){ 17 | var text = ev.target.value.toLowerCase(); 18 | if (!text) { 19 | result.style.display = 'none'; 20 | result.innerHTML = ''; 21 | return; 22 | } 23 | 24 | if (text === prevText) return; 25 | prevText = text; 26 | 27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []}; 28 | var len = searchIndex.length; 29 | var kind; 30 | for (var i = 0; i < len; i++) { 31 | var pair = searchIndex[i]; 32 | if (pair[0].indexOf(text) !== -1) { 33 | kind = pair[3]; 34 | html[kind].push('
  • ' + pair[2] + '
  • '); 35 | } 36 | } 37 | 38 | var innerHTML = ''; 39 | for (kind in html) { 40 | var list = html[kind]; 41 | if (!list.length) continue; 42 | innerHTML += '
  • ' + kind + '
  • \n' + list.join('\n'); 43 | } 44 | result.innerHTML = innerHTML; 45 | if (innerHTML) result.style.display = 'block'; 46 | selectedIndex = -1; 47 | }); 48 | 49 | // down, up and enter key are pressed, select search result. 50 | input.addEventListener('keydown', function(ev){ 51 | if (ev.keyCode === 40) { 52 | // arrow down 53 | var current = result.children[selectedIndex]; 54 | var selected = result.children[selectedIndex + 1]; 55 | if (selected && selected.classList.contains('search-separator')) { 56 | var selected = result.children[selectedIndex + 2]; 57 | selectedIndex++; 58 | } 59 | 60 | if (selected) { 61 | if (current) current.classList.remove('selected'); 62 | selectedIndex++; 63 | selected.classList.add('selected'); 64 | } 65 | } else if (ev.keyCode === 38) { 66 | // arrow up 67 | var current = result.children[selectedIndex]; 68 | var selected = result.children[selectedIndex - 1]; 69 | if (selected && selected.classList.contains('search-separator')) { 70 | var selected = result.children[selectedIndex - 2]; 71 | selectedIndex--; 72 | } 73 | 74 | if (selected) { 75 | if (current) current.classList.remove('selected'); 76 | selectedIndex--; 77 | selected.classList.add('selected'); 78 | } 79 | } else if (ev.keyCode === 13) { 80 | // enter 81 | var current = result.children[selectedIndex]; 82 | if (current) { 83 | var link = current.querySelector('a'); 84 | if (link) location.href = link.href; 85 | } 86 | } else { 87 | return; 88 | } 89 | 90 | ev.preventDefault(); 91 | }); 92 | 93 | // select search result when search result is mouse over. 94 | result.addEventListener('mousemove', function(ev){ 95 | var current = result.children[selectedIndex]; 96 | if (current) current.classList.remove('selected'); 97 | 98 | var li = ev.target; 99 | while (li) { 100 | if (li.nodeName === 'LI') break; 101 | li = li.parentElement; 102 | } 103 | 104 | if (li) { 105 | selectedIndex = Array.prototype.indexOf.call(result.children, li); 106 | li.classList.add('selected'); 107 | } 108 | }); 109 | 110 | // clear search result when body is clicked. 111 | document.body.addEventListener('click', function(ev){ 112 | selectedIndex = -1; 113 | result.style.display = 'none'; 114 | result.innerHTML = ''; 115 | }); 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /docs/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /docs/source.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Source | parserblade 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
    28 | 29 | 35 | 36 |

    Source 35/68

    37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
    FileIdentifierDocumentSizeLinesUpdated
    src/Parser.js-75 %6/81613 byte592020-08-04 00:14:09 (UTC)
    src/errors/NotImplemented.js-100 %1/1222 byte112020-07-06 16:42:39 (UTC)
    src/errors/ParserError.js-100 %1/1353 byte152020-07-02 23:47:30 (UTC)
    src/index.js-0 %0/2245 byte142020-07-05 17:38:00 (UTC)
    src/strategies/Base.js-77 %7/92042 byte812020-08-04 00:14:09 (UTC)
    src/strategies/Csv.js-45 %5/113635 byte1262020-09-05 23:29:55 (UTC)
    src/strategies/Json.js-62 %5/81836 byte732020-08-04 00:14:09 (UTC)
    src/strategies/Xml/XmlTag.js-0 %0/4535 byte232020-08-01 22:37:28 (UTC)
    src/strategies/Xml/index.js-50 %7/148128 byte3402020-09-03 01:55:36 (UTC)
    src/strategies/Yaml.js-50 %3/61197 byte522020-07-02 23:47:30 (UTC)
    src/strategies/index.js-0 %0/4163 byte62020-07-05 19:27:29 (UTC)
    141 |
    142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /esdocs/css/parser.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onhernandes/parserblade/8c8b2bf1fc1baa16c953e121ad7029bf936c8686/esdocs/css/parser.css -------------------------------------------------------------------------------- /esdocs/manual/csv.md: -------------------------------------------------------------------------------- 1 | # CSV 2 | 3 | Works with CSV data. I haven't tested with xlsx or other similar data types yet. 4 | 5 | ## Usage 6 | 7 | Both `csv.parse()` and `csv.stringify()` accepts the data to be parsed/stringified as the first argument, and an option's object as the second. 8 | 9 | ## Parse 10 | 11 | Parses CSV string to JS data, automatically uses first line as headers. Pass data as first argument. 12 | 13 | ```javascript 14 | const assert = require('assert') 15 | const { csv } = require('parserblade') 16 | const input = 'title,platform\nStardew Valley,Steam' 17 | const result = csv.parse(input) 18 | 19 | assert.deepStrictEqual( 20 | result, 21 | [ { title: 'Stardew Valley', platform: 'Steam' } ] 22 | ) 23 | ``` 24 | 25 | ### Parse headers 26 | 27 | Don't use first line as headers. Pass `{ headers: false }` as second parameter. 28 | 29 | ```javascript 30 | const assert = require('assert') 31 | const { csv } = require('parserblade') 32 | const input = 'name,email\nNetflix,contact@netflix.com' 33 | const result = csv.parse(input, { headers: false }) 34 | 35 | assert.deepStrictEqual( 36 | result, 37 | [ 38 | ['name', 'email'], 39 | ['Netflix', 'contact@netflix.com'] 40 | ] 41 | ) 42 | ``` 43 | 44 | Specify headers passing `{ headers: ['name', 'email'] }` 45 | 46 | ```javascript 47 | const assert = require('assert') 48 | const { csv } = require('parserblade') 49 | const input = 'name,email\nNetflix,contact@netflix.com' 50 | const result = csv.parse(input, { headers: false }) 51 | 52 | assert.deepStrictEqual( 53 | result, 54 | [ 55 | { name: 'Netflix', email: 'contact@netflix.com' } 56 | ] 57 | ) 58 | ``` 59 | 60 | Specify a function to transform headers passing `{ headers: header => header.toUpperCase() }` 61 | 62 | ```javascript 63 | const assert = require('assert') 64 | const { csv } = require('parserblade') 65 | const input = 'name,email\nNetflix,contact@netflix.com' 66 | const result = csv.parse(input, { headers: false }) 67 | 68 | assert.deepStrictEqual( 69 | result, 70 | [ 71 | { NAME: 'Netflix', EMAIL: 'contact@netflix.com' } 72 | ] 73 | ) 74 | ``` 75 | 76 | ### Parse with custom delimiters 77 | 78 | Uses custom delimiters. Anything you want! Pass `{ delimiter: ';' }` as option. 79 | ```javascript 80 | const assert = require('assert') 81 | const { csv } = require('parserblade') 82 | const input = 'name;email\nNetflix;contact@netflix.com' 83 | const result = csv.parse(input, { delimiter: ';' }) 84 | 85 | assert.deepStrictEqual( 86 | result, 87 | [ { name: 'Netflix', email: 'contact@netflix.com' } ] 88 | ) 89 | ``` 90 | 91 | ### Parse skipping some lines 92 | 93 | Pass `{ skipLines: 2 }` as option. 94 | 95 | ```javascript 96 | const assert = require('assert') 97 | const { csv } = require('parserblade') 98 | const input = 'coll streaming platforms\nname,email\nNetflix,contact@netflix.com' 99 | const result = csv.parse(input, { skipLines: 2 }) 100 | 101 | assert.deepStrictEqual( 102 | result, 103 | [ { name: 'Netflix', email: 'contact@netflix.com' } ] 104 | ) 105 | ``` 106 | 107 | ### Parse offset 108 | 109 | Pass `{ offset: 2 }` as option. 110 | 111 | ```javascript 112 | const assert = require('assert') 113 | const { csv } = require('parserblade') 114 | const input = 'name,email\nNetflix,contact@netflix.com\nAmazon,contact@amazon.com' 115 | const result = csv.parse(input, { offset: 2 }) 116 | 117 | assert.deepStrictEqual( 118 | result, 119 | [ { name: 'Netflix', email: 'contact@netflix.com' } ] 120 | ) 121 | ``` 122 | 123 | ## Stringify 124 | 125 | Simply transforms JS array of objects into CSV 126 | 127 | ```javascript 128 | const assert = require('assert') 129 | const { csv } = require('parserblade') 130 | const input = [ 131 | { name: 'Netflix', email: 'contact@netflix.com' } 132 | ] 133 | const result = csv.stringify(input) 134 | 135 | assert.equal( 136 | result, 137 | 'name,email\nNetflix,contact@netflix.com' 138 | ) 139 | ``` 140 | 141 | ### Stringify omitting headers 142 | 143 | Pass `{ headers: false }` as options 144 | 145 | ```javascript 146 | const assert = require('assert') 147 | const { csv } = require('parserblade') 148 | const input = [ 149 | { name: 'Netflix', email: 'contact@netflix.com' } 150 | ] 151 | const result = csv.stringify(input) 152 | 153 | assert.equal( 154 | result, 155 | 'Netflix,contact@netflix.com' 156 | ) 157 | ``` 158 | 159 | ### Stringify with custom column names/headers 160 | 161 | Specifying custom columns is easy in many forms, like just pass `{ columns: [ { key: '', header: '' } ] }` as options. 162 | 163 | Or `{ columns: ['name', 'email'] }`. 164 | 165 | Or `{ columns: { name: 'Name', email: 'Email' } }`. 166 | 167 | ```javascript 168 | const assert = require('assert') 169 | const { csv } = require('parserblade') 170 | const input = [ 171 | { name: 'Netflix', email: 'contact@netflix.com' } 172 | ] 173 | 174 | const columns = [ 175 | { key: 'name', header: 'Platform' }, 176 | { key: 'email', header: 'e-mail' } 177 | ] 178 | 179 | const result = csv.stringify(input, { columns }) 180 | 181 | assert.equal( 182 | result, 183 | 'Platform,e-mail\nNetflix,contact@netflix.com' 184 | ) 185 | ``` 186 | 187 | ## Valid 188 | 189 | Just checks if given string is a valid CSV 190 | 191 | ```javascript 192 | const assert = require('assert') 193 | const { csv } = require('parserblade') 194 | const result = csv.valid('name\nstardew,pokemon') 195 | 196 | assert.equal( 197 | result, 198 | false 199 | ) 200 | ``` 201 | 202 | ## Stream 203 | 204 | ### pipeStringify 205 | 206 | Turns JS data into CSV 207 | 208 | ```javascript 209 | const { csv } = require('parserblade') 210 | const { Readable } = require('stream') 211 | const fs = require('fs') 212 | 213 | const input = [{ game: 'Killing Floor' }, { game: 'Stardew Valley' }] 214 | const reader = new Readable({ 215 | objectMode: true, 216 | read (size) { 217 | const next = input.shift() 218 | this.push(next || null) 219 | } 220 | }) 221 | 222 | const writer = csv.pipeStringify() 223 | const toFile = fs.createWriteStream('./data-test.csv') 224 | 225 | reader 226 | .pipe(writer) 227 | .pipe(toFile) 228 | .on('error', console.log) 229 | .on('end', () => { 230 | console.log('done') 231 | }) 232 | ``` 233 | 234 | ### pipeStringify with custom delimiter 235 | 236 | ```javascript 237 | const { csv } = require('parserblade') 238 | const { Readable } = require('stream') 239 | const fs = require('fs') 240 | 241 | const input = [{ game: 'Killing Floor' }, { game: 'Stardew Valley' }] 242 | const reader = new Readable({ 243 | objectMode: true, 244 | read (size) { 245 | const next = input.shift() 246 | this.push(next || null) 247 | } 248 | }) 249 | 250 | const writer = csv.pipeStringify({ delimiter: ';' }) 251 | const toFile = fs.createWriteStream('./data-test.csv') 252 | 253 | reader 254 | .pipe(writer) 255 | .pipe(toFile) 256 | .on('error', console.log) 257 | .on('end', () => { 258 | console.log('done') 259 | }) 260 | ``` 261 | 262 | ### pipeStringify with custom column names 263 | 264 | ```javascript 265 | const { csv } = require('parserblade') 266 | const { Readable } = require('stream') 267 | const fs = require('fs') 268 | 269 | const input = [{ game: 'Killing Floor' }, { game: 'Stardew Valley' }] 270 | const reader = new Readable({ 271 | objectMode: true, 272 | read (size) { 273 | const next = input.shift() 274 | this.push(next || null) 275 | } 276 | }) 277 | 278 | const columns = [ 279 | { key: 'game', header: 'title' } 280 | ] 281 | 282 | const writer = csv.pipeStringify({ columns }) 283 | const toFile = fs.createWriteStream('./data-test.csv') 284 | 285 | reader 286 | .pipe(writer) 287 | .pipe(toFile) 288 | .on('error', console.log) 289 | .on('end', () => { 290 | console.log('done') 291 | }) 292 | ``` 293 | 294 | ### pipeStringify reordering columns 295 | 296 | ```javascript 297 | const { csv } = require('parserblade') 298 | const { Readable } = require('stream') 299 | const fs = require('fs') 300 | 301 | const input = [{ game: 'Killing Floor', platform: 'Windows 10' }, { game: 'Stardew Valley', platform: 'Windows 10' }] 302 | const reader = new Readable({ 303 | objectMode: true, 304 | read (size) { 305 | const next = input.shift() 306 | this.push(next || null) 307 | } 308 | }) 309 | 310 | const columns = [ 311 | { key: 'platform' }, 312 | { key: 'game' } 313 | ] 314 | 315 | const writer = csv.pipeStringify({ columns }) 316 | const toFile = fs.createWriteStream('./data-test.csv') 317 | 318 | reader 319 | .pipe(writer) 320 | .pipe(toFile) 321 | .on('error', console.log) 322 | .on('end', () => { 323 | console.log('done') 324 | }) 325 | ``` 326 | 327 | ### pipeParse 328 | 329 | ```javascript 330 | const { csv } = require('parserblade') 331 | const fs = require('fs') 332 | const path = require('path') 333 | const filepath = path.resolve(__dirname, '../data/services.csv') 334 | 335 | const reader = fs.createReadStream(filepath) 336 | const writer = csv.pipeParse() 337 | 338 | reader 339 | .pipe(writer) 340 | .on('readable', console.log) 341 | .on('error', console.log) 342 | .on('end', () => { 343 | console.log('done') 344 | }) 345 | ``` 346 | 347 | ### pipeParse setting custom delimiter 348 | 349 | ```javascript 350 | const { csv } = require('parserblade') 351 | const fs = require('fs') 352 | const path = require('path') 353 | const filepath = path.resolve(__dirname, '../data/services.csv') 354 | 355 | const reader = fs.createReadStream(filepath) 356 | const writer = csv.pipeParse({ delimiter: ';' }) 357 | 358 | reader 359 | .pipe(writer) 360 | .on('readable', console.log) 361 | .on('error', console.log) 362 | .on('end', () => { 363 | console.log('done') 364 | }) 365 | ``` 366 | 367 | ### pipeParse without using first line as header 368 | 369 | ```javascript 370 | const { csv } = require('parserblade') 371 | const fs = require('fs') 372 | const path = require('path') 373 | const filepath = path.resolve(__dirname, '../data/services.csv') 374 | 375 | const reader = fs.createReadStream(filepath) 376 | const writer = csv.pipeParse({ headers: false }) 377 | 378 | reader 379 | .pipe(writer) 380 | .on('readable', console.log) 381 | .on('error', console.log) 382 | .on('end', () => { 383 | console.log('done') 384 | }) 385 | ``` 386 | -------------------------------------------------------------------------------- /esdocs/manual/json.md: -------------------------------------------------------------------------------- 1 | # JSON 2 | 3 | ## Parse 4 | 5 | There's no magic here. It just calls native's `JSON.parse`, currently there's no additional parameters. 6 | 7 | ```javascript 8 | const assert = require('assert') 9 | const { json } = require('parserblade') 10 | const input = '[{"game":"Stardew Valley"}]' 11 | const result = json.parse(input) 12 | 13 | assert.deepStrictEqual( 14 | result, 15 | [ { game: 'Stardew Valley' } ] 16 | ) 17 | ``` 18 | 19 | ## Stringify 20 | 21 | There's no magic here. It just calls native's `JSON.stringify`, currently there's no additional parameters. 22 | 23 | ```javascript 24 | const assert = require('assert') 25 | const { json } = require('parserblade') 26 | const input = [ { game: 'Stardew Valley' } ] 27 | const result = json.stringify(input) 28 | 29 | assert.equal( 30 | result, 31 | '[{"game":"Stardew Valley"}]' 32 | ) 33 | ``` 34 | 35 | ## Valid 36 | 37 | Just checks if given string is a valid JSON data 38 | 39 | ```javascript 40 | const assert = require('assert') 41 | const { json } = require('parserblade') 42 | const result = json.valid('{') 43 | 44 | assert.equal( 45 | result, 46 | false 47 | ) 48 | ``` 49 | 50 | ## Stream 51 | 52 | ### Stringify an array 53 | 54 | ```javascript 55 | const { json } = require('parserblade') 56 | const { Readable } = require('stream') 57 | const fs = require('fs') 58 | 59 | const input = [{ game: 'Killing Floor' }, { game: 'Stardew Valley' }] 60 | const reader = new Readable({ 61 | objectMode: true, 62 | read (size) { 63 | const next = input.shift() 64 | 65 | if (!next) { 66 | this.push(null) 67 | } else { 68 | this.push(next) 69 | } 70 | } 71 | }) 72 | 73 | const writer = json.pipeStringify() 74 | const toFile = fs.createWriteStream('./data-test.json') 75 | 76 | reader 77 | .pipe(writer) 78 | .pipe(toFile) 79 | .on('error', console.log) 80 | .on('end', () => { 81 | console.log('done') 82 | }) 83 | ``` 84 | 85 | ### Stringify an object 86 | 87 | You must pass `{ type: 'object' }` as param. Defaults to `array`. 88 | 89 | Data must be an array of `[ key, value ]`. Like from `Object.entries({ game: 'Stardew Valley' })` 90 | 91 | ```javascript 92 | const { json } = require('parserblade') 93 | const { Readable } = require('stream') 94 | const fs = require('fs') 95 | 96 | const input = Object.entries({ 97 | name: 'Rodolfo' 98 | }) 99 | 100 | const reader = new Readable({ 101 | objectMode: true, 102 | read (size) { 103 | const next = input.shift() 104 | 105 | if (!next) { 106 | this.push(null) 107 | } else { 108 | this.push(next) 109 | } 110 | } 111 | }) 112 | 113 | const writer = json.pipeStringify({ type: 'object' }) 114 | const toFile = fs.createWriteStream('./data-test.json') 115 | 116 | reader 117 | .pipe(writer) 118 | .pipe(toFile) 119 | .on('error', console.log) 120 | .on('end', () => { 121 | console.log('done') 122 | }) 123 | ``` 124 | 125 | ### Parse 126 | 127 | ```javascript 128 | const { json } = require('parserblade') 129 | const fs = require('fs') 130 | const path = require('path') 131 | const filepath = path.resolve(__dirname, '../data/services.json') 132 | 133 | const reader = fs.createReadStream(filepath) 134 | const writer = json.pipeParse() 135 | 136 | reader 137 | .pipe(writer) 138 | .on('data', console.log) 139 | .on('error', console.log) 140 | .on('end', () => { 141 | console.log('done') 142 | }) 143 | ``` 144 | -------------------------------------------------------------------------------- /esdocs/manual/xml.md: -------------------------------------------------------------------------------- 1 | # XML 2 | 3 | Works with XML data. I haven't tested with xlsx or other similar data types yet. There's a lot of things to improve here. 4 | 5 | ## Usage 6 | 7 | Both `xml.parse()` and `xml.stringify()` accepts the data to be parsed/stringified as the first argument, and an option's object as the second. 8 | 9 | ### Parse 10 | 11 | ```javascript 12 | const assert = require('assert') 13 | const { xml } = require('parserblade') 14 | const input = 'Naruto Shippuden Storm 3playstation' 15 | const result = xml.parse(input) 16 | 17 | assert.deepStrictEqual( 18 | result, 19 | { 20 | games: { 21 | name: { _text: 'Naruto Shippuden Storm 3' }, 22 | platform: { _text: 'playstation' } 23 | } 24 | } 25 | ) 26 | ``` 27 | 28 | ```javascript 29 | const assert = require('assert') 30 | const { xml } = require('parserblade') 31 | const input = 'mongoosesequelize' 32 | const result = xml.parse(input) 33 | 34 | assert.deepStrictEqual( 35 | result, 36 | { 37 | packages: { 38 | name: [ 39 | { _text: 'mongoose' }, 40 | { _text: 'sequelize' } 41 | ] 42 | } 43 | } 44 | ) 45 | ``` 46 | 47 | #### Parse XML including declaration 48 | 49 | Pass `{ showDeclaration: true }` as option. 50 | 51 | ```javascript 52 | const assert = require('assert') 53 | const { xml } = require('parserblade') 54 | const input = 'mongoosesequelize' 55 | const result = xml.parse(input, { showDeclaration: true }) 56 | 57 | assert.deepStrictEqual( 58 | result, 59 | { 60 | _declaration: { 61 | _attributes: { 62 | encoding: 'utf-8', 63 | version: 1 64 | } 65 | }, 66 | packages: { 67 | name: [ 68 | { _text: 'mongoose' }, 69 | { _text: 'sequelize' } 70 | ] 71 | } 72 | } 73 | ) 74 | ``` 75 | 76 | #### Parse XML in verbose mode 77 | 78 | Pass `{ verbose: true }` as option. 79 | 80 | ```javascript 81 | const assert = require('assert') 82 | const { xml } = require('parserblade') 83 | const input = 'Naruto Shippuden Storm 3playstation' 84 | const result = xml.parse(input, { verbose: true }) 85 | const expected = { 86 | elements: [ 87 | { 88 | type: 'element', 89 | name: 'games', 90 | elements: [ 91 | { 92 | type: 'element', 93 | name: 'name', 94 | elements: [ 95 | { 96 | type: 'text', 97 | text: 'Naruto Shippuden Storm 3' 98 | } 99 | ] 100 | }, 101 | { 102 | type: 'element', 103 | name: 'platform', 104 | elements: [ 105 | { 106 | type: 'text', 107 | text: 'playstation' 108 | } 109 | ] 110 | }, 111 | ] 112 | } 113 | ] 114 | } 115 | 116 | assert.deepStrictEqual( 117 | result, 118 | expected 119 | ) 120 | ``` 121 | 122 | ### Stringify 123 | 124 | ```javascript 125 | const assert = require('assert') 126 | const { xml } = require('parserblade') 127 | const input = { game: 'Stardew Valley' } 128 | const result = xml.stringify(input) 129 | 130 | assert.deepStrictEqual( 131 | result, 132 | 'Stardew Valley' 133 | ) 134 | ``` 135 | 136 | #### Stringify without XML declaration 137 | 138 | ```javascript 139 | const assert = require('assert') 140 | const { xml } = require('parserblade') 141 | const input = { game: 'Stardew Valley' } 142 | const result = xml.stringify(input, { ignoreDeclaration: true }) 143 | 144 | assert.deepStrictEqual( 145 | result, 146 | 'Stardew Valley' 147 | ) 148 | ``` 149 | 150 | #### Stringify array 151 | 152 | ```javascript 153 | const assert = require('assert') 154 | const { xml } = require('parserblade') 155 | const input = { 156 | packages: [ 157 | { name: 'lodash' } 158 | ] 159 | } 160 | const result = xml.stringify(input) 161 | 162 | assert.deepStrictEqual( 163 | result, 164 | 'lodash' 165 | ) 166 | ``` 167 | 168 | #### Stringify with metadata 169 | 170 | ```javascript 171 | const assert = require('assert') 172 | const { xml } = require('parserblade') 173 | const input = { 174 | packages: [ 175 | { 176 | _text: 'lodash', 177 | _attributes: { lang: 'nodejs' } 178 | }, 179 | { 180 | _text: 'flash', 181 | _attributes: { lang: 'python' } 182 | } 183 | ] 184 | } 185 | const result = xml.stringify(input) 186 | 187 | assert.deepStrictEqual( 188 | result, 189 | 'lodashflash' 190 | ) 191 | ``` 192 | 193 | ### Valid 194 | 195 | Just checks if given string is a valid XML 196 | 197 | ```javascript 198 | const assert = require('assert') 199 | const { xml } = require('parserblade') 200 | const result = xml.valid('phrase') 201 | 202 | assert.equal( 203 | result, 204 | false 205 | ) 206 | ``` 207 | 208 | ## Stream 209 | 210 | ### pipeParse 211 | 212 | You may specify in which depth it should emit data, defaults to 0. 213 | 214 | ```javascript 215 | const { Readable } = require('stream') 216 | const { xml } = require('parserblade') 217 | const input = ` 218 | 219 | 220 | Naruto Shippuden Storm 3 221 | 222 | platform 223 | 224 | This is another tag 225 | 226 | 227 | Third tag another 228 | 229 | 230 | 231 | Netflix 232 | 233 | Possible description here 234 | 235 | 236 | 237 | `.split('') 238 | 239 | const reader = new Readable({ 240 | read () { 241 | const next = input.shift() 242 | if (typeof next === 'string') { 243 | this.push(next) 244 | } else { 245 | this.push(null) 246 | } 247 | } 248 | }) 249 | 250 | reader 251 | .pipe(xml.pipeParse()) 252 | .on('data', console.log) 253 | .on('error', console.log) 254 | .on('end', () => console.log('stream ended')) 255 | ``` 256 | 257 | ### pipeStringify 258 | 259 | You can set which tag wraps everything with `{ mainTag: { name, text, attributes } }` 260 | 261 | ```javascript 262 | const { Readable } = require('stream') 263 | const { xml } = require('parserblade') 264 | const input = [{ name: 'Starcraft II' }] 265 | 266 | const reader = new Readable({ 267 | objectMode: true, 268 | read () { 269 | const next = input.shift() 270 | this.push(next || null) 271 | } 272 | }) 273 | 274 | reader 275 | .pipe(xml.pipeParse({ mainTag: { name: 'games' } })) 276 | .on('data', console.log) 277 | .on('error', console.log) 278 | .on('end', () => console.log('stream ended')) 279 | ``` 280 | -------------------------------------------------------------------------------- /esdocs/manual/yaml.md: -------------------------------------------------------------------------------- 1 | # YAML 2 | 3 | ## Usage 4 | 5 | Both `yaml.parse()` and `yaml.stringify()` accepts the data to be parsed/stringified as the first argument, and an option's object as the second. 6 | 7 | ### Parse 8 | 9 | ```javascript 10 | const assert = require('assert'') 11 | const { yaml } = require('parserblade') 12 | const input = 'series: Bleach\nseasons: 16' 13 | const result = yaml.parse(data) 14 | assert.deepStrictEqual( 15 | result, 16 | { series: 'Bleach', seasons: 16 } 17 | ) 18 | ``` 19 | 20 | ### Stringify 21 | 22 | ```javascript 23 | const assert = require('assert'') 24 | const { yaml } = require('parserblade') 25 | const input = { series: 'Bleach', seasons: 16 } 26 | const result = yaml.parse(data) 27 | assert.equal( 28 | result, 29 | 'series: Bleach\nseasons: 16' 30 | ) 31 | ``` 32 | 33 | ### Valid 34 | 35 | Just checks if given string is a valid YAML 36 | 37 | ```javascript 38 | const assert = require('assert') 39 | const { yaml } = require('parserblade') 40 | const result = yaml.valid('[name:\nStardew') 41 | 42 | assert.equal( 43 | result, 44 | false 45 | ) 46 | ``` 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parserblade", 3 | "version": "1.2.0", 4 | "description": "The easiest parser for JSON, XML, CSV and YAML. Use it as simple as JSON.stringify() or JSON.parse(). All in one place.", 5 | "homepage": "https://onhernandes.github.io/parserblade", 6 | "author": { 7 | "name": "Matheus Hernandes", 8 | "email": "", 9 | "url": "onhernandes.github.io" 10 | }, 11 | "files": [ 12 | "src" 13 | ], 14 | "main": "src/index.js", 15 | "keywords": [ 16 | "parser", 17 | "data-parser", 18 | "stream", 19 | "stream-parser", 20 | "streaming-parser", 21 | "csv-parser", 22 | "csv-parser-stream", 23 | "csv-stringify", 24 | "csv-stringify-stream", 25 | "transform-csv", 26 | "csv-streaming", 27 | "csv", 28 | "yaml-parser", 29 | "yaml-stringify", 30 | "yaml-stringify-stream", 31 | "yaml-parser-stream", 32 | "transform-yaml", 33 | "yaml", 34 | "xml-parser", 35 | "xml-stringify", 36 | "xml-parser-stream", 37 | "xml-stringify-stream", 38 | "transform-xml", 39 | "xml", 40 | "json", 41 | "json-parser", 42 | "json-stringify", 43 | "json-stringify-stream", 44 | "json-parser-stream" 45 | ], 46 | "devDependencies": { 47 | "esdoc": "^1.1.0", 48 | "esdoc-ecmascript-proposal-plugin": "^1.0.0", 49 | "esdoc-inject-style-plugin": "^1.0.0", 50 | "esdoc-standard-plugin": "^1.0.0", 51 | "eslint": "^7.4.0", 52 | "eslint-config-standard": "^14.1.1", 53 | "eslint-plugin-import": "^2.22.0", 54 | "eslint-plugin-node": "^11.1.0", 55 | "eslint-plugin-promise": "^4.2.1", 56 | "eslint-plugin-standard": "^4.0.1", 57 | "husky": "^3.0.9", 58 | "jest": "^26.1.0", 59 | "lint-staged": "^9.4.3" 60 | }, 61 | "engines": { 62 | "npm": ">= 4.0.0" 63 | }, 64 | "lint-staged": { 65 | "*.js": [ 66 | "eslint --fix", 67 | "git add" 68 | ] 69 | }, 70 | "husky": { 71 | "hooks": { 72 | "pre-commit": "lint-staged" 73 | } 74 | }, 75 | "scripts": { 76 | "test": "./node_modules/.bin/jest --no-cache", 77 | "tdd": "./node_modules/.bin/jest --watch --no-cache", 78 | "docs": "./node_modules/.bin/esdoc", 79 | "lint": "./node_modules/.bin/eslint", 80 | "publish-docs": "./node_modules/.bin/esdoc && git subtree push --prefix docs origin gh-pages", 81 | "tarfile": "tar -czvf parserblade.tar.gz LICENSE package* README.md src" 82 | }, 83 | "repository": "git@github.com:onhernandes/parserblade.git", 84 | "jest": { 85 | "testEnvironment": "node" 86 | }, 87 | "license": "MIT", 88 | "dependencies": { 89 | "JSONStream": "^1.3.5", 90 | "csv-parse": "^4.11.1", 91 | "csv-stringify": "^5.5.0", 92 | "js-yaml": "^3.14.0", 93 | "node-xml-stream": "^1.0.2", 94 | "xml-js": "^1.6.11" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parser - Receives any strategy and safely implement it 3 | * 4 | * @constructor 5 | * @param {Base} strategy - Any strategy implementing the Base's prototype 6 | */ 7 | function Parser (strategy) { 8 | this.strategy = strategy 9 | } 10 | 11 | /** 12 | * Parser.prototype.parse - Exposes the parsing from string to any valid JS type with the strategy 13 | * 14 | * @param {string} data 15 | * @param {object} options 16 | */ 17 | Parser.prototype.parse = function parse (data, options) { 18 | return this.strategy.parse(data, options) 19 | } 20 | 21 | /** 22 | * Parser.prototype.stringify - Exposes the stringify() method from any valid JS type to expected type with the strategy 23 | * 24 | * @param {*} data 25 | * @param {object} options 26 | */ 27 | Parser.prototype.stringify = function stringify (data, options) { 28 | return this.strategy.stringify(data, options) 29 | } 30 | 31 | /** 32 | * Parser.prototype.valid - Exposes the valid() method from strategy. Checks if given data is valid 33 | * 34 | * @param {string} data 35 | * @param {object} options 36 | */ 37 | Parser.prototype.valid = function stringify (data, options) { 38 | return this.strategy.valid(data, options) 39 | } 40 | 41 | /** 42 | * Parser.prototype.pipeStringify - Exposes the pipeStringify() method from strategy. Streams data through stringify 43 | */ 44 | Parser.prototype.pipeStringify = function pipeStringify () { 45 | return this.strategy.pipeStringify() 46 | } 47 | 48 | /** 49 | * Parser.prototype.pipeParse - Exposes the pipeParse() method from strategy. Streams data through parse 50 | */ 51 | Parser.prototype.pipeParse = function pipeParse () { 52 | return this.strategy.pipeParse() 53 | } 54 | 55 | Parser.prototype.get = function get (data, path) {} 56 | 57 | Parser.prototype.has = function has (data, path) {} 58 | 59 | module.exports = Parser 60 | -------------------------------------------------------------------------------- /src/errors/NotImplemented.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotImplemented 3 | */ 4 | function NotImplemented () { 5 | this.name = 'NotImplemented' 6 | this.message = 'This method haven\'t been implemented yet' 7 | } 8 | 9 | NotImplemented.prototype = new Error() 10 | 11 | module.exports = NotImplemented 12 | -------------------------------------------------------------------------------- /src/errors/ParserError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ParseError 3 | * 4 | * @param {string} format - which format the error ocurred 5 | * @param {object} context - any context info for debugging 6 | */ 7 | function ParseError (format, context = {}) { 8 | this.name = 'ParseError' 9 | this.message = `Failed to parse ${format}` 10 | this.context = context 11 | } 12 | 13 | ParseError.prototype = new Error() 14 | 15 | module.exports = ParseError 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | Json, 3 | Xml, 4 | Yaml, 5 | Csv 6 | } = require('./strategies') 7 | const Parser = require('./Parser') 8 | 9 | module.exports = { 10 | json: new Parser(new Json()), 11 | xml: new Parser(new Xml()), 12 | yaml: new Parser(new Yaml()), 13 | csv: new Parser(new Csv()) 14 | } 15 | -------------------------------------------------------------------------------- /src/strategies/Base.js: -------------------------------------------------------------------------------- 1 | const NotImplemented = require('../errors/NotImplemented') 2 | const ParserError = require('../errors/ParserError') 3 | 4 | /** 5 | * Base class for strategies around the Parser 6 | * Every function that haven't been implemented 7 | * will throw an NotImplementedError 8 | * 9 | * @constructor 10 | */ 11 | function Base () {} 12 | 13 | /** 14 | * Base.prototype.stringify - receives * form of data and turns it into a string 15 | * 16 | * @param {*} data 17 | * @param {object} options 18 | * @throws {NotImplemented} This method must be implemented 19 | */ 20 | Base.prototype.stringify = function stringify (data, options) { 21 | throw new NotImplemented() 22 | } 23 | 24 | /** 25 | * Base.prototype.parse - receives an string and translate it to valid JavaScript 26 | * 27 | * @param {string} data 28 | * @param {object} options 29 | * @throws {NotImplemented} This method must be implemented 30 | */ 31 | Base.prototype.parse = function parse (data, options) { 32 | throw new NotImplemented() 33 | } 34 | 35 | /** 36 | * Base.prototype.pipe - prototype for streams 37 | * 38 | * @throws {NotImplemented} This method must be implemented 39 | */ 40 | Base.prototype.pipe = function pipe () { 41 | throw new NotImplemented() 42 | } 43 | 44 | /** 45 | * Base.prototype.valid - checks if a given value is valid in desired format using the implemented method parse(), ignoring any exception 46 | * 47 | * @param {object} options - any option accepted for the implemented method parse() 48 | * @returns {boolean} wether or not the given data is a valid format 49 | */ 50 | Base.prototype.valid = function valid (data, options = {}) { 51 | try { 52 | this.parse(data, options) 53 | return true 54 | } catch (error) { 55 | if (error instanceof ParserError) { 56 | return false 57 | } 58 | 59 | throw error 60 | } 61 | } 62 | 63 | /** 64 | * Base.prototype.pipeParse - prototype for streams 65 | * 66 | * @throws {NotImplemented} This method must be implemented 67 | */ 68 | Base.prototype.pipeParse = function pipeParse () { 69 | throw new NotImplemented() 70 | } 71 | 72 | /** 73 | * Base.prototype.pipeStringify - prototype for streams 74 | * 75 | * @throws {NotImplemented} This method must be implemented 76 | */ 77 | Base.prototype.pipeStringify = function pipeStringify () { 78 | throw new NotImplemented() 79 | } 80 | 81 | module.exports = Base 82 | -------------------------------------------------------------------------------- /src/strategies/Csv.js: -------------------------------------------------------------------------------- 1 | const Base = require('./Base') 2 | const ParserError = require('../errors/ParserError') 3 | const csvParser = require('csv-parse/lib/sync') 4 | const csvStringify = require('csv-stringify/lib/sync') 5 | const csvParserStream = require('csv-parse') 6 | const csvStringifyStream = require('csv-stringify') 7 | 8 | /** 9 | * Csv - Support for CSV filetype 10 | * 11 | * @constructor 12 | */ 13 | function Csv () {} 14 | 15 | Csv.prototype = Object.create(Base.prototype) 16 | 17 | /** 18 | * Csv.prototype.parse - receives an CSV string and returns valid JS 19 | * 20 | * @param {string} data 21 | * @param {object} [options] 22 | * @param {(boolean|array|function)} [options.headers] - If should parse first line as the headers, default is true 23 | * @param {(string|Buffer)} [options.delimiter] - Which delimiters to use when parsing, defaults to comma `,` 24 | * @param {number} [options.skipLines] - How many lines it should skip before parsing, defaults to 1 25 | * @param {number} [options.offset] - How many lines it should parse, defaults to -1 26 | * @returns {array} 27 | */ 28 | Csv.prototype.parse = function parse (data, options = {}) { 29 | const config = { 30 | columns: true, 31 | skip_empty_lines: true, 32 | delimiter: options.delimiter || ',', 33 | from_line: options.skipLines || 1 34 | } 35 | 36 | if (Object.prototype.hasOwnProperty.apply(options, ['headers'])) { 37 | config.columns = options.headers 38 | } 39 | 40 | if (options.offset) { 41 | config.to_line = options.offset 42 | } 43 | 44 | try { 45 | return csvParser(data, config) 46 | } catch (e) { 47 | const context = { 48 | code: e.code, 49 | message: e.message, 50 | column: e.column, 51 | emptyLines: e.empty_lines, 52 | header: e.header, 53 | index: e.index, 54 | lines: e.lines, 55 | quoting: e.quoting, 56 | records: e.records 57 | } 58 | 59 | throw new ParserError('csv', context) 60 | } 61 | } 62 | 63 | /** 64 | * Csv.prototype.stringify - receives * valid JS data and returns it as CSV 65 | * 66 | * @param {array} data 67 | * @param {object} [options] 68 | * @param {boolean} [options.headers] - If should set first line as the headers, default is true 69 | * @param {(array|object)} [options.columns] - Custom column mapping, see examples for more 70 | * @returns {string} 71 | */ 72 | Csv.prototype.stringify = function stringify (data, options = {}) { 73 | const config = { 74 | header: true 75 | } 76 | 77 | if (options.headers === false) { 78 | config.header = false 79 | } 80 | 81 | if (options.columns) { 82 | config.columns = options.columns 83 | } 84 | 85 | return csvStringify(data, config) 86 | } 87 | 88 | /** 89 | * Csv.prototype.pipeParse - allow streaming data 90 | * 91 | * @param {object} [options] 92 | * @param {(boolean|array|function)} [options.headers] - If should parse first line as the headers, default is true 93 | * @param {string} [options.delimiter] - Which delimiters to use when parsing, defaults to comma `,` 94 | */ 95 | Csv.prototype.pipeParse = function pipeParse (options = {}) { 96 | const config = { 97 | delimiter: options.delimiter || ',', 98 | columns: Reflect.has(options, 'headers') ? options.headers : true 99 | } 100 | 101 | return csvParserStream(config) 102 | } 103 | 104 | /** 105 | * Csv.prototype.pipeStringify - stream 106 | * 107 | * @param {array} data 108 | * @param {object} [options] 109 | * @param {boolean} [options.headers] - If should set first line as the headers, default is true 110 | * @param {string} [options.delimiter] - Which delimiters to use when parsing, defaults to comma `,` 111 | * @param {(array|object)} [options.columns] - Custom column mapping, see examples for more 112 | */ 113 | Csv.prototype.pipeStringify = function pipeStringify (options = {}) { 114 | const config = { 115 | delimiter: options.delimiter || ',', 116 | header: Reflect.has(options, 'headers') ? !!options.headers : true 117 | } 118 | 119 | if (Reflect.has(options, 'columns')) { 120 | config.columns = options.columns 121 | } 122 | 123 | return csvStringifyStream(config) 124 | } 125 | 126 | module.exports = Csv 127 | -------------------------------------------------------------------------------- /src/strategies/Json.js: -------------------------------------------------------------------------------- 1 | const Base = require('./Base') 2 | const ParserError = require('../errors/ParserError') 3 | const JSONStream = require('JSONStream') 4 | 5 | /** 6 | * Json - Support for JSON filetype 7 | * 8 | * @constructor 9 | */ 10 | function Json () {} 11 | 12 | Json.prototype = Object.create(Base.prototype) 13 | 14 | /** 15 | * Json.prototype.parse - receives an JSON string and returns valid JS 16 | * 17 | * @param {string} data 18 | * @throws {ParserError} In case the JSON string is not valid, ParserError will be thrown 19 | * @returns {*} 20 | */ 21 | Json.prototype.parse = function parse (data) { 22 | try { 23 | return JSON.parse(data) 24 | } catch (e) { 25 | throw new ParserError('json') 26 | } 27 | } 28 | 29 | /** 30 | * Json.prototype.stringify - receives * valid JS data and returns it as JSON 31 | * 32 | * @param {*} data 33 | * @returns {string} 34 | */ 35 | Json.prototype.stringify = function stringify (data) { 36 | return JSON.stringify(data) 37 | } 38 | 39 | /** 40 | * Json.prototype.pipeStringify - helps to stream object or array into JSON valid data 41 | * 42 | * @param {object} [config] - sets config for stream 43 | * @param {string} [config.type='array'] - which type of data you're streaming, defaults do array 44 | * @returns {WritableStream} 45 | */ 46 | Json.prototype.pipeStringify = function pipeStringify (config = {}) { 47 | config.type = config.type || 'array' 48 | const streams = { 49 | object: JSONStream.stringifyObject, 50 | array: JSONStream.stringify 51 | } 52 | 53 | const fn = streams[config.type] 54 | 55 | if (!fn) { 56 | throw new ParserError(`Supplied type "${config.type}" is not allowed. Use either "array" or "object"`) 57 | } 58 | 59 | return fn() 60 | } 61 | 62 | /** 63 | * Json.prototype.pipeStringify - helps to stream JSON data to JS 64 | * 65 | * @param {object} [config] - sets config for stream 66 | * @param {string} [config.path] - select which data path to be parsed from JSON to JS 67 | * @returns {Stream} 68 | */ 69 | Json.prototype.pipeParse = function pipeParse (config) { 70 | return JSONStream.parse() 71 | } 72 | 73 | module.exports = Json 74 | -------------------------------------------------------------------------------- /src/strategies/Xml/XmlTag.js: -------------------------------------------------------------------------------- 1 | function XmlDeclaration (version, encoding) { 2 | this.name = 'declaration' 3 | this.version = version 4 | this.encoding = encoding 5 | } 6 | 7 | function XmlTag (name, value, attributes, tags) { 8 | this.name = name 9 | this.value = value 10 | this.attributes = attributes 11 | this.tags = tags 12 | } 13 | 14 | XmlTag.prototype.reset = function reset () { 15 | return new XmlTag(this.name, this.value, this.attributes, this.tags) 16 | } 17 | 18 | function XmlCharacterData (cdata) { 19 | this.name = 'cdata' 20 | this.cdata = cdata 21 | } 22 | 23 | module.exports = { XmlTag, XmlDeclaration, XmlCharacterData } 24 | -------------------------------------------------------------------------------- /src/strategies/Xml/index.js: -------------------------------------------------------------------------------- 1 | const Base = require('../Base') 2 | const ParserError = require('../../errors/ParserError') 3 | const xml = require('xml-js') 4 | const NotImplemented = require('../../errors/NotImplemented') 5 | const { Transform } = require('stream') 6 | const StreamParser = require('node-xml-stream') 7 | const { XmlTag, XmlCharacterData, XmlDeclaration } = require('./XmlTag') 8 | 9 | /** 10 | * Xml - Support for XML filetype 11 | * 12 | * @constructor 13 | */ 14 | function Xml () { 15 | this.XML_VERSION_TAG = { 16 | _declaration: { 17 | _attributes: { 18 | version: '1.0', 19 | encoding: 'utf-8' 20 | } 21 | } 22 | } 23 | 24 | this.XML_JS_KEYS = { 25 | declarationKey: '_declaration', 26 | instructionKey: '_instruction', 27 | attributesKey: '_attributes', 28 | textKey: '_text', 29 | cdataKey: '_cdata', 30 | doctypeKey: '_doctype', 31 | commentKey: '_comment', 32 | parentKey: '_parent', 33 | typeKey: '_type', 34 | nameKey: '_name', 35 | elementsKey: '_elements' 36 | } 37 | } 38 | 39 | Xml.prototype = Object.create(Base.prototype) 40 | 41 | /** 42 | * Xml.prototype.setXmlDeclaration - sets XML declaration tag on first position of array or object 43 | * 44 | * @param {(object|array)} data - input data 45 | * @returns {(object|array)} 46 | */ 47 | Xml.prototype.setXmlDeclaration = function setXmlDeclaration (data) { 48 | if (Array.isArray(data)) { 49 | data = [this.XML_VERSION_TAG, ...data] 50 | } else { 51 | data = { ...this.XML_VERSION_TAG, ...data } 52 | } 53 | 54 | return data 55 | } 56 | 57 | /** 58 | * Xml.prototype.stringify - receives * valid JS data and returns it as XML 59 | * 60 | * @param {(object|array)} data 61 | * @param {Object} [options] - options for turning JS data into XML 62 | * @param {boolean} [options.ignoreDeclaration] - don't output XML version tag, default is true 63 | * @returns {string} 64 | */ 65 | Xml.prototype.stringify = function stringify (data, options = {}) { 66 | const config = { 67 | compact: true, 68 | ignoreDeclaration: false 69 | } 70 | 71 | data = this.setXmlDeclaration(data) 72 | 73 | if (options.ignoreDeclaration) { 74 | config.ignoreDeclaration = true 75 | } 76 | 77 | return xml.js2xml(data, config) 78 | } 79 | 80 | /** 81 | * Xml.prototype.parse - receives an XML string and translate it to valid JavaScript 82 | * 83 | * @param {string} data 84 | * @param {object} [options] 85 | * @param {boolean} [options.showDeclaration] - force parsing XML declaration tag 86 | * @param {boolean} [options.verbose] - makes xml2js return non compact mode, defaults to false 87 | * @param {boolean} [options.experimentalXmlTag] - use experimental XmlTag prototype, default is false 88 | * @throws {NotImplemented} This method must be implemented 89 | */ 90 | Xml.prototype.parse = function parse (data, options = {}) { 91 | try { 92 | const config = { 93 | compact: true, 94 | ignoreDeclaration: true, 95 | nativeType: true, 96 | nativeTypeAttributes: true 97 | } 98 | 99 | if (options.showDeclaration) { 100 | config.ignoreDeclaration = false 101 | } 102 | 103 | if (options.verbose) { 104 | config.compact = false 105 | } 106 | 107 | const result = xml.xml2js(data, config) 108 | 109 | if (options.experimentalXmlTag) { 110 | return this.toXmlTag(result) 111 | } 112 | 113 | return result 114 | } catch (error) { 115 | throw new ParserError(error.message) 116 | } 117 | } 118 | 119 | /** 120 | * Xml.prototype.toXmlTag - turns xml2js non-compact result into XmlTag and XmlResult 121 | * 122 | * @param {object} xml2jsResult 123 | * @throws {NotImplemented} 124 | */ 125 | Xml.prototype.toXmlTag = function toXmlTag (xml2jsResult) { 126 | throw new NotImplemented() 127 | } 128 | 129 | /** 130 | * Xml.prototype.pipeParse - stream 131 | * 132 | * @param {object} [options] 133 | * @param {Number} [options.depth=0] 134 | */ 135 | Xml.prototype.pipeParse = function pipeParse (options = {}) { 136 | options.depth = options.depth || 0 137 | const parser = new StreamParser() 138 | 139 | let index = 0 140 | let parsedTags = new Map() 141 | const toEmit = [] 142 | const lastTag = { 143 | index: null, 144 | name: null, 145 | tagIndex: null 146 | } 147 | 148 | const getFirstTagName = map => { 149 | if (map.has(0) === false) { 150 | return null 151 | } 152 | 153 | const mapPosZero = map.get(0) 154 | const arrayMap = Array.from(mapPosZero) 155 | 156 | if (arrayMap.length === 0) { 157 | return null 158 | } 159 | 160 | const keyValue = arrayMap[0] 161 | 162 | if (keyValue.length === 0) { 163 | return null 164 | } 165 | 166 | return keyValue[0] 167 | } 168 | 169 | parser.on('opentag', (name, attrs) => { 170 | const inheritFrom = { 171 | index: null, 172 | name: null 173 | } 174 | 175 | if (index >= 1) { 176 | const beforeIndex = index - 1 177 | const beforeKey = [ 178 | ...parsedTags 179 | .get(beforeIndex) 180 | .keys() 181 | ].reverse()[0] 182 | inheritFrom.index = beforeIndex 183 | inheritFrom.name = beforeKey 184 | } 185 | 186 | if (!parsedTags.has(index)) { 187 | parsedTags.set(index, new Map()) 188 | } 189 | 190 | if (!parsedTags.get(index).has(name)) { 191 | parsedTags.get(index).set(name, []) 192 | } 193 | 194 | const tag = new XmlTag(name, null, attrs, []) 195 | tag.inheritFrom = inheritFrom 196 | 197 | lastTag.index = index 198 | lastTag.name = name 199 | lastTag.tagIndex = parsedTags.get(index).get(name).push(tag) - 1 200 | tag.inheritFrom.tagIndex = lastTag.tagIndex 201 | index = index + 1 202 | }) 203 | 204 | parser.on('text', (text) => { 205 | parsedTags 206 | .get(lastTag.index) 207 | .get(lastTag.name)[lastTag.tagIndex] 208 | .value = text 209 | 210 | lastTag.index = null 211 | lastTag.name = null 212 | lastTag.tagIndex = null 213 | }) 214 | 215 | parser.on('closetag', (name) => { 216 | index = index - 1 217 | 218 | if (index === options.depth) { 219 | /** 220 | * must reorganize data to a single object 221 | * them emit it 222 | */ 223 | let entries = Array.from(parsedTags).reverse() 224 | entries = entries.map( 225 | ([intIndex, tagsMap]) => ({ 226 | intIndex, tagsMap: Array.from(tagsMap).reverse() 227 | }) 228 | ) 229 | entries.pop() 230 | entries.forEach(entry => { 231 | const intIndex = entry.intIndex === 0 ? entry.intIndex : entry.intIndex - 1 232 | const indexedTags = parsedTags.get(intIndex) 233 | 234 | entry.tagsMap.forEach(tag => { 235 | const list = tag[1] 236 | list.forEach(tagToBePushed => { 237 | indexedTags 238 | .get(tagToBePushed.inheritFrom.name)[0] 239 | .tags 240 | .push(tagToBePushed.reset()) 241 | }) 242 | }) 243 | }) 244 | 245 | parsedTags 246 | .get(index) 247 | .get(name) 248 | .forEach(tag => toEmit.push(tag.reset())) 249 | } 250 | 251 | if (name === getFirstTagName(parsedTags)) { 252 | parsedTags = new Map() 253 | } 254 | }) 255 | 256 | parser.on('cdata', cdata => { 257 | const CData = new XmlCharacterData(cdata) 258 | toEmit.push(CData) 259 | }) 260 | 261 | parser.on('instruction', (name, attrs) => { 262 | const declaration = new XmlDeclaration(attrs.version, attrs.encoding) 263 | toEmit.push(declaration) 264 | }) 265 | 266 | return new Transform({ 267 | objectMode: true, 268 | transform (chunk, encoding, ack) { 269 | parser.write(chunk.toString()) 270 | 271 | if (toEmit.length > 0) { 272 | this.push(toEmit.shift()) 273 | } 274 | 275 | ack() 276 | } 277 | }) 278 | } 279 | 280 | /** 281 | * Xml.prototype.pipeStringify - stream from JS data into XML 282 | * 283 | * @param {object} [options] - all options to stringify 284 | * @param {object} [options.mainTag] - the wrapping tag 285 | * @param {string} [options.mainTag.name] - the wrapping tag's name 286 | */ 287 | Xml.prototype.pipeStringify = function pipeStringify (options = {}) { 288 | options.mainTag = options.mainTag || {} 289 | const defaultContent = 'FAKE_CONTENT' 290 | const name = options.mainTag.name 291 | const contents = options.mainTag.text || defaultContent 292 | const tag = { [name || 'fake']: contents } 293 | const stringified = this.stringify(tag) 294 | 295 | const lastIndexOfArrow = stringified.lastIndexOf('<') 296 | let initialTag = stringified.substr(0, lastIndexOfArrow) 297 | 298 | if (initialTag.indexOf(defaultContent) !== -1) { 299 | initialTag.replace(defaultContent, '') 300 | } 301 | 302 | let endingTag = stringified.substr( 303 | lastIndexOfArrow, 304 | stringified.length 305 | ) 306 | 307 | if (Reflect.has(options.mainTag, 'name') === false) { 308 | const firstArrowIndex = initialTag.indexOf('>') + 1 309 | initialTag = initialTag.substr(0, firstArrowIndex) 310 | endingTag = '' 311 | } 312 | 313 | const xml = this 314 | let isFirstData = true 315 | 316 | return new Transform({ 317 | objectMode: true, 318 | transform (chunk, encoding, ack) { 319 | const options = { 320 | ignoreDeclaration: true 321 | } 322 | 323 | if (isFirstData) { 324 | this.push(initialTag) 325 | isFirstData = false 326 | } 327 | 328 | const toBePushed = xml.stringify(chunk, options) 329 | this.push(toBePushed) 330 | 331 | ack() 332 | }, 333 | flush (cb) { 334 | this.push(endingTag) 335 | cb() 336 | } 337 | }) 338 | } 339 | 340 | module.exports = Xml 341 | -------------------------------------------------------------------------------- /src/strategies/Yaml.js: -------------------------------------------------------------------------------- 1 | const Base = require('./Base') 2 | const ParserError = require('../errors/ParserError') 3 | const yaml = require('js-yaml') 4 | 5 | /** 6 | * Yaml - Support for YAML filetype 7 | * 8 | * @constructor 9 | */ 10 | function Yaml () { 11 | } 12 | 13 | Yaml.prototype = Object.create(Base.prototype) 14 | 15 | /** 16 | * Yaml.prototype.stringify - receives * valid JS data and returns it as YAML 17 | * 18 | * @param {object} data 19 | * @param {Object} options - options for turning JS data into YAML 20 | * @throws {ParserError} For invalid data type 21 | * @returns {string} 22 | */ 23 | Yaml.prototype.stringify = function stringify (data, options = {}) { 24 | if (Array.isArray(data)) { 25 | throw new ParserError('Only plain objects are accepted for stringify()') 26 | } 27 | 28 | return yaml.safeDump(data) 29 | } 30 | 31 | /** 32 | * Yaml.prototype.parse - receives an YAML string and translate it to valid JavaScript 33 | * 34 | * @param {string} data 35 | * @param {object} options 36 | * @returns {object} Parsed YAML data as JS object 37 | */ 38 | Yaml.prototype.parse = function parse (data, options = {}) { 39 | try { 40 | return yaml.safeLoad(data) 41 | } catch (e) { 42 | const context = { 43 | errorName: e.name, 44 | message: e.message, 45 | mark: e.mark 46 | } 47 | 48 | throw new ParserError('yaml', context) 49 | } 50 | } 51 | 52 | module.exports = Yaml 53 | -------------------------------------------------------------------------------- /src/strategies/index.js: -------------------------------------------------------------------------------- 1 | const Json = require('./Json') 2 | const Xml = require('./Xml') 3 | const Yaml = require('./Yaml') 4 | const Csv = require('./Csv') 5 | 6 | module.exports = { Json, Xml, Yaml, Csv } 7 | -------------------------------------------------------------------------------- /tests/Parser.test.js: -------------------------------------------------------------------------------- 1 | const Parser = require('../src/Parser') 2 | 3 | describe('Parser implements Strategy', () => { 4 | it('calls parse() strategy method', () => { 5 | const mock = { parse: jest.fn() } 6 | const parser = new Parser(mock) 7 | parser.parse() 8 | expect(mock.parse).toHaveBeenCalled() 9 | }) 10 | 11 | it('calls stringify() strategy method', () => { 12 | const mock = { stringify: jest.fn() } 13 | const parser = new Parser(mock) 14 | parser.stringify() 15 | expect(mock.stringify).toHaveBeenCalled() 16 | }) 17 | 18 | it('calls valid() strategy method', () => { 19 | const mock = { valid: jest.fn() } 20 | const parser = new Parser(mock) 21 | parser.valid() 22 | expect(mock.valid).toHaveBeenCalled() 23 | }) 24 | 25 | it('calls pipeStringify() strategy method', () => { 26 | const mock = { pipeStringify: jest.fn() } 27 | const parser = new Parser(mock) 28 | parser.pipeStringify() 29 | expect(mock.pipeStringify).toHaveBeenCalled() 30 | }) 31 | 32 | it('calls pipeParse() strategy method', () => { 33 | const mock = { pipeParse: jest.fn() } 34 | const parser = new Parser(mock) 35 | parser.pipeParse() 36 | expect(mock.pipeParse).toHaveBeenCalled() 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /tests/data/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": [ 3 | { 4 | "url": "netflix.com" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/strategies/Base.test.js: -------------------------------------------------------------------------------- 1 | const Base = require('../../src/strategies/Base') 2 | const NotImplemented = require('../../src/errors/NotImplemented') 3 | 4 | function Implementation () { 5 | Base.call(this) 6 | } 7 | 8 | Implementation.prototype = Object.create(Base.prototype) 9 | 10 | const instance = new Implementation() 11 | 12 | describe('Base Strategy implementation', function () { 13 | it('throws NotImplemented for stringify() method', () => { 14 | expect(instance.stringify).toThrow(NotImplemented) 15 | }) 16 | 17 | it('throws NotImplemented for parse() method', () => { 18 | expect(instance.parse).toThrow(NotImplemented) 19 | }) 20 | 21 | it('throws NotImplemented for pipe() method', () => { 22 | expect(instance.pipe).toThrow(NotImplemented) 23 | }) 24 | 25 | it('throws NotImplemented for pipeParse() method', () => { 26 | expect(instance.pipeParse).toThrow(NotImplemented) 27 | }) 28 | 29 | it('throws NotImplemented for pipeStringify() method', () => { 30 | expect(instance.pipeStringify).toThrow(NotImplemented) 31 | }) 32 | 33 | /* 34 | it('throws NotImplemented for valid() method, because parse() is not implemented', () => { 35 | expect(instance.valid).toThrow(NotImplemented) 36 | }) 37 | */ 38 | }) 39 | -------------------------------------------------------------------------------- /tests/strategies/Csv.test.js: -------------------------------------------------------------------------------- 1 | const Csv = require('../../src/strategies/Csv') 2 | const strategy = new Csv() 3 | const { Readable } = require('stream') 4 | 5 | const input = [ 6 | 'name,email', 7 | 'Netflix,contact@netflix.com', 8 | 'Prime Video,contact@primevideo.com' 9 | ] 10 | 11 | const getReader = (inputArray, options = {}) => new Readable({ 12 | objectMode: !!options.objectMode, 13 | read () { 14 | const next = inputArray.shift() 15 | this.push(next || null) 16 | } 17 | }) 18 | 19 | describe('Csv Strategy', () => { 20 | describe('Csv.prototype.parse()', function () { 21 | it('parse CSV string ignoring headers', () => { 22 | const input = 'name,email\nNetflix,contact@netflix.com' 23 | const result = strategy.parse(input, { headers: false }) 24 | expect(result).toEqual([ 25 | ['name', 'email'], 26 | ['Netflix', 'contact@netflix.com'] 27 | ]) 28 | }) 29 | 30 | it('parse CSV string with headers', () => { 31 | const input = 'name,email\nNetflix,contact@netflix.com' 32 | const result = strategy.parse(input) 33 | 34 | expect(result).toEqual([ 35 | { name: 'Netflix', email: 'contact@netflix.com' } 36 | ]) 37 | }) 38 | 39 | it('parse CSV string with custom delimiters', () => { 40 | const input = 'name;email\nNetflix;contact@netflix.com' 41 | const result = strategy.parse(input, { delimiter: ';' }) 42 | 43 | expect(result).toEqual([ 44 | { name: 'Netflix', email: 'contact@netflix.com' } 45 | ]) 46 | }) 47 | 48 | it('parse CSV string skipping lines', () => { 49 | const input = 'insights\nname,email\nNetflix,contact@netflix.com' 50 | const result = strategy.parse(input, { skipLines: 2 }) 51 | 52 | expect(result).toEqual([ 53 | { name: 'Netflix', email: 'contact@netflix.com' } 54 | ]) 55 | }) 56 | 57 | it('parse CSV string with offset', () => { 58 | const input = 'name,email\nNetflix,contact@netflix.com\nAmazon,contact@amazon.com' 59 | const result = strategy.parse(input, { offset: 2 }) 60 | 61 | expect(result).toEqual([ 62 | { name: 'Netflix', email: 'contact@netflix.com' } 63 | ]) 64 | }) 65 | }) 66 | 67 | describe('Csv.prototype.stringify()', function () { 68 | it('turns array of objects into CSV string', () => { 69 | const input = [ 70 | { name: 'Netflix', email: 'contact@netflix.com' } 71 | ] 72 | const result = strategy.stringify(input) 73 | 74 | expect(result).toEqual(expect.stringMatching('name,email\nNetflix,contact@netflix.com')) 75 | }) 76 | 77 | it('turns array of objects into CSV string without header', () => { 78 | const input = [ 79 | { name: 'Netflix', email: 'contact@netflix.com' } 80 | ] 81 | const result = strategy.stringify(input, { headers: false }) 82 | 83 | expect(result).toEqual(expect.stringMatching('Netflix,contact@netflix.com')) 84 | }) 85 | 86 | it('turns array of objects into CSV string with custom column names', () => { 87 | const input = [ 88 | { name: 'Netflix', email: 'contact@netflix.com' } 89 | ] 90 | const columns = [ 91 | { key: 'name', header: 'Platform' }, 92 | { key: 'email', header: 'e-mail' } 93 | ] 94 | 95 | const result = strategy.stringify(input, { columns }) 96 | 97 | expect(result).toEqual(expect.stringMatching('Platform,e-mail\nNetflix,contact@netflix.com')) 98 | }) 99 | }) 100 | 101 | describe('Csv.prototype.valid', function () { 102 | it('returns false for invalid input data', () => { 103 | const result = strategy.valid('name\nstardew,pokemon') 104 | expect(result).toBe(false) 105 | }) 106 | 107 | it('returns true for valid input data', () => { 108 | const result = strategy.valid('name,email\nNetflix,contact@netflix.com') 109 | expect(result).toBe(true) 110 | }) 111 | }) 112 | 113 | describe('Csv.prototype.pipeParse', () => { 114 | it('parses with default options', () => { 115 | const reader = getReader(Array.from(input)) 116 | const parsedData = [] 117 | reader 118 | .pipe(strategy.pipeParse()) 119 | .on('readable', data => { 120 | parsedData.push(data) 121 | }) 122 | .on('error', console.log) 123 | .on('end', () => { 124 | expect(parsedData).toHaveLength(2) 125 | 126 | const netflix = { 127 | name: 'Netflix', 128 | email: 'contact@netflix.com' 129 | } 130 | expect(parsedData[0]).toMatchObject(netflix) 131 | 132 | const prime = { 133 | name: 'Prime Video', 134 | email: 'contact@primevideo.com' 135 | } 136 | expect(parsedData[1]).toMatchObject(prime) 137 | }) 138 | }) 139 | 140 | it('parses with custom options.delimiter', () => { 141 | const input = [ 142 | 'name;email', 143 | 'Netflix;contact@netflix.com', 144 | 'Prime Video;contact@primevideo.com' 145 | ] 146 | const reader = getReader(Array.from(input)) 147 | const parsedData = [] 148 | reader 149 | .pipe(strategy.pipeParse({ delimiter: ';' })) 150 | .on('readable', data => { 151 | parsedData.push(data) 152 | }) 153 | .on('error', console.log) 154 | .on('end', () => { 155 | expect(parsedData).toHaveLength(2) 156 | 157 | const netflix = { 158 | name: 'Netflix', 159 | email: 'contact@netflix.com' 160 | } 161 | expect(parsedData[0]).toMatchObject(netflix) 162 | 163 | const prime = { 164 | name: 'Prime Video', 165 | email: 'contact@primevideo.com' 166 | } 167 | expect(parsedData[1]).toMatchObject(prime) 168 | }) 169 | }) 170 | }) 171 | 172 | describe('Csv.prototype.pipeStringify', () => { 173 | it('stringify with default options', () => { 174 | const input = [ 175 | { name: 'Netflix', site: 'netflix.com' }, 176 | { name: 'Prime Video', site: 'primevideo.com' } 177 | ] 178 | const reader = getReader( 179 | Array.from(input), 180 | { objectMode: true } 181 | ) 182 | 183 | const parsedData = [] 184 | reader 185 | .pipe(strategy.pipeStringify()) 186 | .on('readable', function () { 187 | let row 188 | 189 | while (row = this.read()) { // eslint-disable-line 190 | parsedData.push(row.toString()) 191 | } 192 | }) 193 | .on('error', e => { throw e }) 194 | .on('end', () => { 195 | const str = parsedData[0] 196 | expect(str).toEqual('name,site\nNetflix,netflix.com\nPrime Video,primevideo.com\n') 197 | }) 198 | }) 199 | 200 | it('stringify with custom delimiter', () => { 201 | const input = [ 202 | { name: 'Netflix', site: 'netflix.com' }, 203 | { name: 'Prime Video', site: 'primevideo.com' } 204 | ] 205 | const reader = getReader( 206 | Array.from(input), 207 | { objectMode: true } 208 | ) 209 | 210 | const parsedData = [] 211 | reader 212 | .pipe(strategy.pipeStringify({ delimiter: ';' })) 213 | .on('readable', function () { 214 | let row 215 | 216 | while (row = this.read()) { // eslint-disable-line 217 | parsedData.push(row.toString()) 218 | } 219 | }) 220 | .on('error', e => { throw e }) 221 | .on('end', () => { 222 | const str = parsedData[0] 223 | expect(str).toEqual('name;site\nNetflix;netflix.com\nPrime Video;primevideo.com\n') 224 | }) 225 | }) 226 | 227 | it('stringify with custom column', () => { 228 | const input = [ 229 | { name: 'Netflix', site: 'netflix.com' }, 230 | { name: 'Prime Video', site: 'primevideo.com' } 231 | ] 232 | const reader = getReader( 233 | Array.from(input), 234 | { objectMode: true } 235 | ) 236 | 237 | const config = { 238 | columns: [ 239 | { key: 'name', header: 'Name' }, 240 | { key: 'site', header: 'Website URL' } 241 | ] 242 | } 243 | 244 | const parsedData = [] 245 | reader 246 | .pipe(strategy.pipeStringify(config)) 247 | .on('readable', function () { 248 | let row 249 | 250 | while (row = this.read()) { // eslint-disable-line 251 | parsedData.push(row.toString()) 252 | } 253 | }) 254 | .on('error', e => { throw e }) 255 | .on('end', () => { 256 | const str = parsedData[0] 257 | expect(str).toEqual('Name,Website URL\nNetflix,netflix.com\nPrime Video,primevideo.com\n') 258 | }) 259 | }) 260 | 261 | it('stringify reordering columns', () => { 262 | const input = [ 263 | { name: 'Netflix', site: 'netflix.com' }, 264 | { name: 'Prime Video', site: 'primevideo.com' } 265 | ] 266 | const reader = getReader( 267 | Array.from(input), 268 | { objectMode: true } 269 | ) 270 | 271 | const config = { 272 | columns: [ 273 | { key: 'site' }, 274 | { key: 'name' } 275 | ] 276 | } 277 | 278 | const parsedData = [] 279 | reader 280 | .pipe(strategy.pipeStringify(config)) 281 | .on('readable', function () { 282 | let row 283 | 284 | while (row = this.read()) { // eslint-disable-line 285 | parsedData.push(row.toString()) 286 | } 287 | }) 288 | .on('error', e => { throw e }) 289 | .on('end', () => { 290 | const str = parsedData[0] 291 | expect(str).toEqual('site,name\nnetflix.com,Netflix\nprimevideo.com,Prime Video\n') 292 | }) 293 | }) 294 | }) 295 | }) 296 | -------------------------------------------------------------------------------- /tests/strategies/Json.test.js: -------------------------------------------------------------------------------- 1 | const Json = require('../../src/strategies/Json') 2 | const ParserError = require('../../src/errors/ParserError') 3 | const NotImplemented = require('../../src/errors/NotImplemented') 4 | const Stream = require('stream') 5 | const strategy = new Json() 6 | const fs = require('fs') 7 | const path = require('path') 8 | 9 | const TEST_FILE = path.resolve(__dirname, '../data/services.json') 10 | 11 | describe('Json Strategy', function () { 12 | describe('Json.prototype.parse', function () { 13 | it('parses JSON object string to JS object properly', function () { 14 | const str = '{}' 15 | expect(strategy.parse(str)).toEqual({}) 16 | }) 17 | 18 | it('throws ParserError for invalid JSON string', function () { 19 | try { 20 | const str = '}' 21 | strategy.parse(str) 22 | } catch (e) { 23 | expect(e).toBeInstanceOf(ParserError) 24 | } 25 | }) 26 | }) 27 | 28 | describe('Json.prototype.stringify', function () { 29 | it('transforms JS object into JSON string', () => { 30 | const data = { name: 'Hernandes', package: 'parser' } 31 | expect(strategy.stringify(data)).toBe('{"name":"Hernandes","package":"parser"}') 32 | }) 33 | }) 34 | 35 | describe('Json.prototype.pipe', function () { 36 | it('throws NotImplemented error for pipe()', () => { 37 | expect(strategy.pipe).toThrow(NotImplemented) 38 | }) 39 | }) 40 | 41 | describe('Json.prototype.valid', function () { 42 | it('returns false for invalid input data', () => { 43 | const result = strategy.valid('}') 44 | expect(result).toBe(false) 45 | }) 46 | 47 | it('returns true for valid array as input data', () => { 48 | const result = strategy.valid('[]') 49 | expect(result).toBe(true) 50 | }) 51 | 52 | it('returns true for valid object as input data', () => { 53 | const result = strategy.valid('{}') 54 | expect(result).toBe(true) 55 | }) 56 | }) 57 | 58 | describe('Json.prototype.pipeStringify', () => { 59 | it('stringifies an array of objects', () => { 60 | const input = [{ game: 'Killing Floor' }, { game: 'Stardew Valley' }] 61 | 62 | const reader = new Stream.Readable({ 63 | objectMode: true, 64 | read (size) { 65 | const next = input.shift() 66 | 67 | if (!next) { 68 | this.push(null) 69 | } else { 70 | this.push(next) 71 | } 72 | } 73 | }) 74 | 75 | const result = [] 76 | const writer = strategy.pipeStringify() 77 | reader.pipe(writer) 78 | 79 | writer.on('data', (data) => { 80 | result.push(data) 81 | }) 82 | 83 | writer.on('error', console.log) 84 | writer.on('end', () => { 85 | const jsonString = result.join('') 86 | const parsed = JSON.parse(jsonString) 87 | expect(parsed).toEqual(expect.arrayContaining(input)) 88 | }) 89 | }) 90 | 91 | it('stringifies an object', () => { 92 | const input = { 93 | services: [ 94 | { url: 'cloud.google.com' } 95 | ] 96 | } 97 | const entries = Object.entries(input) 98 | 99 | const reader = new Stream.Readable({ 100 | objectMode: true, 101 | read (size) { 102 | const next = entries.shift() 103 | 104 | if (!next) { 105 | this.push(null) 106 | } else { 107 | this.push(next) 108 | } 109 | } 110 | }) 111 | 112 | const result = [] 113 | const writer = strategy.pipeStringify({ type: 'object' }) 114 | reader.pipe(writer) 115 | 116 | writer.on('data', (data) => { 117 | result.push(data) 118 | }) 119 | 120 | writer.on('error', console.log) 121 | writer.on('end', () => { 122 | const jsonString = result.join('') 123 | const parsed = JSON.parse(jsonString) 124 | expect(parsed).toMatchObject(input) 125 | }) 126 | }) 127 | }) 128 | 129 | describe('Json.prototype.pipeParse', () => { 130 | it('parses an object', () => { 131 | const reader = fs.createReadStream(TEST_FILE) 132 | 133 | const result = [] 134 | const writer = strategy.pipeParse() 135 | reader.pipe(writer) 136 | 137 | writer.on('data', (data) => { 138 | result.push(data) 139 | }) 140 | 141 | writer.on('error', console.log) 142 | 143 | writer.on('end', () => { 144 | expect(result).toHaveLength(1) 145 | expect(result[0]).toMatchObject({ 146 | services: [{ url: 'netflix.com' }] 147 | }) 148 | }) 149 | }) 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /tests/strategies/Xml.test.js: -------------------------------------------------------------------------------- 1 | const Xml = require('../../src/strategies/Xml') 2 | const ParserError = require('../../src/errors/ParserError') 3 | const NotImplemented = require('../../src/errors/NotImplemented') 4 | const strategy = new Xml() 5 | const { Readable } = require('stream') 6 | const { XmlTag, XmlDeclaration } = require('../../src/strategies/Xml/XmlTag') 7 | 8 | const input = ` 9 | 10 | 11 | Naruto Shippuden Storm 3 12 | 13 | platform 14 | 15 | This is another tag 16 | 17 | 18 | Third tag another 19 | 20 | 21 | 22 | Netflix 23 | 24 | Possible description here 25 | 26 | 27 | 28 | `.split('') 29 | 30 | const getReader = (inputArray, options = {}) => new Readable({ 31 | objectMode: !!options.objectMode, 32 | read () { 33 | const next = inputArray.shift() 34 | if (next) { 35 | this.push(next) 36 | } else { 37 | this.push(null) 38 | } 39 | } 40 | }) 41 | 42 | describe('Xml Strategy', function () { 43 | describe('Xml.prototype.setXmlDeclaration()', function () { 44 | it('puts XML declaration on first position within array', () => { 45 | const data = [{ language: 'nodejs' }] 46 | const result = strategy.setXmlDeclaration(data) 47 | expect(result[0]).toEqual(strategy.XML_VERSION_TAG) 48 | }) 49 | 50 | it('puts XML declaration on first position within object', () => { 51 | const data = { language: 'nodejs' } 52 | const result = strategy.setXmlDeclaration(data) 53 | const keys = Object.keys(result) 54 | expect(keys[0]).toEqual('_declaration') 55 | }) 56 | }) 57 | 58 | describe('Xml.prototype.stringify()', function () { 59 | it('transforms JS object into Xml string', () => { 60 | const data = { game: 'Stardew Valley' } 61 | const expected = 'Stardew Valley' 62 | expect(strategy.stringify(data)).toBe(expected) 63 | }) 64 | 65 | it('transforms JS object into XML string without xml version', () => { 66 | const data = { game: 'Stardew Valley' } 67 | const expected = 'Stardew Valley' 68 | const result = strategy.stringify(data, { ignoreDeclaration: true }) 69 | expect(result).toBe(expected) 70 | }) 71 | 72 | it('transforms JS array into XML string', () => { 73 | const data = { 74 | packages: [ 75 | { name: 'lodash' } 76 | ] 77 | } 78 | const expected = 'lodash' 79 | const result = strategy.stringify(data) 80 | expect(result).toBe(expected) 81 | }) 82 | }) 83 | 84 | describe('Xml.prototype.parse()', function () { 85 | it('parses XML string to JS object in verbose mode', function () { 86 | const data = 'Naruto Shippuden Storm 3playstation' 87 | const expected = { 88 | elements: [ 89 | { 90 | type: 'element', 91 | name: 'games', 92 | elements: [ 93 | { 94 | type: 'element', 95 | name: 'name', 96 | elements: [ 97 | { 98 | type: 'text', 99 | text: 'Naruto Shippuden Storm 3' 100 | } 101 | ] 102 | }, 103 | { 104 | type: 'element', 105 | name: 'platform', 106 | elements: [ 107 | { 108 | type: 'text', 109 | text: 'playstation' 110 | } 111 | ] 112 | } 113 | ] 114 | } 115 | ] 116 | } 117 | const result = strategy.parse(data, { verbose: true }) 118 | expect(result).toStrictEqual(expected) 119 | }) 120 | 121 | it('parses XML string to JS object', function () { 122 | const data = 'Naruto Shippuden Storm 3playstation' 123 | const expected = { 124 | games: { 125 | name: { _text: 'Naruto Shippuden Storm 3' }, 126 | platform: { _text: 'playstation' } 127 | } 128 | } 129 | const result = strategy.parse(data) 130 | expect(result).toStrictEqual(expected) 131 | }) 132 | 133 | it('parses XML string to JS object array', function () { 134 | const data = 'mongoosesequelize' 135 | const expected = { 136 | packages: { 137 | name: [ 138 | { _text: 'mongoose' }, 139 | { _text: 'sequelize' } 140 | ] 141 | } 142 | } 143 | const result = strategy.parse(data) 144 | expect(result).toStrictEqual(expected) 145 | }) 146 | 147 | it('throws ParserError for missing parent tag', function () { 148 | const data = 'mongoosesequelize' 149 | try { 150 | strategy.parse(data) 151 | } catch (error) { 152 | expect(error).toBeInstanceOf(ParserError) 153 | } 154 | }) 155 | 156 | it('parses XML string, including _declaration', function () { 157 | const data = 'mongoosesequelize' 158 | const expected = { 159 | _declaration: { 160 | _attributes: { 161 | encoding: 'utf-8', 162 | version: 1 163 | } 164 | }, 165 | packages: { 166 | name: [ 167 | { _text: 'mongoose' }, 168 | { _text: 'sequelize' } 169 | ] 170 | } 171 | } 172 | const result = strategy.parse(data, { showDeclaration: true }) 173 | expect(result).toStrictEqual(expected) 174 | }) 175 | }) 176 | 177 | describe('Xml.prototype.pipe()', function () { 178 | it('throws NotImplemented error for pipe()', () => { 179 | expect(strategy.pipe).toThrow(NotImplemented) 180 | }) 181 | }) 182 | 183 | describe('Xml.prototype.valid()', function () { 184 | it('returns false for invalid input data', () => { 185 | const result = strategy.valid('phrase') 186 | expect(result).toBe(false) 187 | }) 188 | 189 | it('returns true for valid input data', () => { 190 | const result = strategy.valid('Stardew Valley') 191 | expect(result).toBe(true) 192 | }) 193 | }) 194 | 195 | describe('Xml.prototype.pipeParse', () => { 196 | it('parses with default options.depth', () => { 197 | const reader = getReader(Array.from(input)) 198 | const toExpect = { 199 | declaration: data => { 200 | expect(data).toBeInstanceOf(XmlDeclaration) 201 | expect(data.version).toEqual('1.0') 202 | expect(data.encoding).toEqual('utf-8') 203 | }, 204 | games: data => { 205 | expect(data).toBeInstanceOf(XmlTag) 206 | expect(data.tags).toHaveLength(3) 207 | } 208 | } 209 | reader 210 | .pipe(strategy.pipeParse()) 211 | .on('data', data => { 212 | toExpect[data.name](data) 213 | }) 214 | .on('error', console.log) 215 | .on('end', () => {}) 216 | }) 217 | 218 | it('parses with custom options.depth 1', () => { 219 | const reader = getReader(Array.from(input)) 220 | const toExpect = { 221 | declaration: data => { 222 | expect(data).toBeInstanceOf(XmlDeclaration) 223 | expect(data.version).toEqual('1.0') 224 | expect(data.encoding).toEqual('utf-8') 225 | }, 226 | name: data => { 227 | expect(data).toBeInstanceOf(XmlTag) 228 | expect(data.tags).toHaveLength(0) 229 | }, 230 | platform: data => { 231 | expect(data).toBeInstanceOf(XmlTag) 232 | expect(data.tags).toHaveLength(2) 233 | }, 234 | site: data => { 235 | expect(data).toBeInstanceOf(XmlTag) 236 | expect(data.tags).toHaveLength(1) 237 | } 238 | } 239 | reader 240 | .pipe(strategy.pipeParse({ depth: 1 })) 241 | .on('data', data => { 242 | toExpect[data.name](data) 243 | }) 244 | .on('error', console.log) 245 | .on('end', () => {}) 246 | }) 247 | }) 248 | 249 | describe('Xml.prototype.pipeStringify', () => { 250 | it('stringifies an array of object', () => { 251 | const object = { 252 | games: 'none' 253 | } 254 | 255 | const contents = [object, object] 256 | 257 | const reader = getReader(contents, { objectMode: true }) 258 | 259 | const expected = strategy.stringify(object) + strategy.stringify(object, { ignoreDeclaration: true }) 260 | let parsed = '' 261 | reader 262 | .pipe(strategy.pipeStringify()) 263 | .on('data', data => { 264 | parsed = parsed + data 265 | }) 266 | .on('error', console.log) 267 | .on('end', () => { 268 | expect(parsed).toBe(expected) 269 | }) 270 | }) 271 | 272 | it('stringifies an array of object with custom parent', () => { 273 | const object = { 274 | games: 'none' 275 | } 276 | 277 | const contents = [object, object] 278 | 279 | const reader = getReader(contents, { objectMode: true }) 280 | 281 | const expected = 'All my gamesnonenone' 282 | 283 | const mainTag = { 284 | name: 'my-games', 285 | text: 'All my games' 286 | } 287 | let parsed = '' 288 | reader 289 | .pipe(strategy.pipeStringify({ mainTag })) 290 | .on('data', data => { 291 | parsed = parsed + data 292 | }) 293 | .on('error', console.log) 294 | .on('end', () => { 295 | expect(parsed).toBe(expected) 296 | }) 297 | }) 298 | }) 299 | }) 300 | -------------------------------------------------------------------------------- /tests/strategies/Yaml.test.js: -------------------------------------------------------------------------------- 1 | const Yaml = require('../../src/strategies/Yaml') 2 | const ParserError = require('../../src/errors/ParserError') 3 | const NotImplemented = require('../../src/errors/NotImplemented') 4 | 5 | const strategy = new Yaml() 6 | 7 | describe('Yaml Parser', () => { 8 | describe('Yaml.prototype.parse()', function () { 9 | it('parses YAML to JS object', () => { 10 | const data = 'series: Bleach\nseasons: 16' 11 | const result = strategy.parse(data) 12 | expect(result).toEqual({ series: 'Bleach', seasons: 16 }) 13 | }) 14 | }) 15 | 16 | describe('Yaml.prototype.stringify()', function () { 17 | it('turns JS into YAML', () => { 18 | const data = { series: 'Bleach', seasons: 16 } 19 | const result = strategy.stringify(data) 20 | const expected = 'series: Bleach\nseasons: 16' 21 | 22 | expect(result).toEqual(expect.stringMatching(expected)) 23 | }) 24 | 25 | it('throws ParserError when calling stringify() with array data', () => { 26 | try { 27 | strategy.stringify([]) 28 | } catch (error) { 29 | expect(error).toBeInstanceOf(ParserError) 30 | } 31 | }) 32 | }) 33 | 34 | describe('Yaml.prototype.pipe()', function () { 35 | it('throws NotImplemented error for pipe()', () => { 36 | expect(strategy.pipe).toThrow(NotImplemented) 37 | }) 38 | }) 39 | 40 | describe('Yaml.prototype.valid()', function () { 41 | it('returns false for invalid input data', () => { 42 | const result = strategy.valid('[name:\nStardew') 43 | expect(result).toBe(false) 44 | }) 45 | 46 | it('returns true for valid input data', () => { 47 | const result = strategy.valid('name:"Stardew Valley"') 48 | expect(result).toBe(true) 49 | }) 50 | }) 51 | }) 52 | --------------------------------------------------------------------------------