├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── rss-parser.js ├── rss-parser.js.map ├── rss-parser.min.js └── rss-parser.min.js.map ├── index.d.ts ├── index.js ├── lib ├── fields.js ├── parser.js └── utils.js ├── package-lock.json ├── package.json ├── scripts └── build.sh ├── test ├── browser.js ├── html.js ├── index.html ├── input │ ├── atom-customfields.atom │ ├── content-encoded.rss │ ├── craigslist.rss │ ├── customfields.rss │ ├── encoding.rss │ ├── feedburner.atom │ ├── giantbomb-podcast.rss │ ├── guardian.rss │ ├── gulp-atom.atom │ ├── heise.atom │ ├── heraldsun.rss │ ├── incomplete-fields.atom │ ├── instant-article.rss │ ├── item-itunes-episodeType.rss │ ├── itunes-category.rss │ ├── itunes-href.rss │ ├── itunes-keywords-array.rss │ ├── itunes-keywords-astext.rss │ ├── itunes-keywords.rss │ ├── itunes-missing-image.rss │ ├── many-links.rss │ ├── missing-fields.atom │ ├── narro.rss │ ├── pagination-links.rss │ ├── reddit-atom.rss │ ├── reddit-home.rss │ ├── reddit.rss │ ├── rss-1.rss │ ├── unrecognized.rss │ ├── uolNoticias.rss │ └── xml2js-options.rss ├── output │ ├── atom-customfields.json │ ├── content-encoded.json │ ├── craigslist.json │ ├── customfields.json │ ├── encoding.json │ ├── feedburner.json │ ├── giantbomb-podcast.json │ ├── guardian.json │ ├── gulp-atom.json │ ├── heise.json │ ├── heraldsun.json │ ├── incomplete-fields.json │ ├── instant-article.json │ ├── item-itunes-episodeType.json │ ├── itunes-category.json │ ├── itunes-href.json │ ├── itunes-keywords-array.json │ ├── itunes-keywords-astext.json │ ├── itunes-keywords.json │ ├── many-links.json │ ├── missing-fields.json │ ├── narro.json │ ├── pagination-links.json │ ├── reddit-atom.json │ ├── reddit-home.json │ ├── reddit.json │ ├── rss-1.json │ ├── uolNoticias.json │ └── xml2js-options.json └── parser.js └── webpack.config.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: tests 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm ci 24 | - run: npm test 25 | - run: npm run build 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.swp 3 | .idea/ 4 | dist/stats.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.swp 3 | .idea/ 4 | dist/stats.json 5 | test 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | language: node_js 4 | node_js: 5 | - "8" 6 | 7 | before_script: 8 | - npm install -g mocha 9 | script: npm test 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - libnss3 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bobby Brennan 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rss-parser 2 | 3 | [![Version][npm-image]][npm-link] 4 | [![Build Status][build-image]][build-link] 5 | [![Downloads][downloads-image]][npm-link] 6 | 7 | [downloads-image]: https://img.shields.io/npm/dm/rss-parser.svg 8 | [npm-image]: https://img.shields.io/npm/v/rss-parser.svg 9 | [npm-link]: https://npmjs.org/package/rss-parser 10 | [build-image]: https://github.com/rbren/rss-parser/workflows/tests/badge.svg 11 | [build-link]: https://github.com/rbren/rss-parser/actions 12 | 13 | A small library for turning RSS XML feeds into JavaScript objects. 14 | 15 | ## Installation 16 | ```bash 17 | npm install --save rss-parser 18 | ``` 19 | 20 | ## Usage 21 | You can parse RSS from a URL (`parser.parseURL`) or an XML string (`parser.parseString`). 22 | 23 | Both callbacks and Promises are supported. 24 | 25 | ### NodeJS 26 | Here's an example in NodeJS using Promises with async/await: 27 | 28 | ```js 29 | let Parser = require('rss-parser'); 30 | let parser = new Parser(); 31 | 32 | (async () => { 33 | 34 | let feed = await parser.parseURL('https://www.reddit.com/.rss'); 35 | console.log(feed.title); 36 | 37 | feed.items.forEach(item => { 38 | console.log(item.title + ':' + item.link) 39 | }); 40 | 41 | })(); 42 | ``` 43 | 44 | ### TypeScript 45 | When using TypeScript, you can set a type to control the custom fields: 46 | 47 | ```typescript 48 | import Parser from 'rss-parser'; 49 | 50 | type CustomFeed = {foo: string}; 51 | type CustomItem = {bar: number}; 52 | 53 | const parser: Parser = new Parser({ 54 | customFields: { 55 | feed: ['foo', 'baz'], 56 | // ^ will error because `baz` is not a key of CustomFeed 57 | item: ['bar'] 58 | } 59 | }); 60 | 61 | (async () => { 62 | const parser = new Parser(); 63 | const feed = await parser.parseURL('https://www.reddit.com/.rss'); 64 | console.log(feed.title); // feed will have a `foo` property, type as a string 65 | 66 | feed.items.forEach(item => { 67 | console.log(item.title + ':' + item.link) // item will have a `bar` property type as a number 68 | }); 69 | })(); 70 | ``` 71 | 72 | ### Web 73 | > We recommend using a bundler like [webpack](https://webpack.js.org/), but we also provide 74 | > pre-built browser distributions in the `dist/` folder. If you use the pre-built distribution, 75 | > you'll need a [polyfill](https://github.com/taylorhakes/promise-polyfill) for Promise support. 76 | 77 | Here's an example in the browser using callbacks: 78 | 79 | ```html 80 | 81 | 97 | ``` 98 | 99 | ### Upgrading from v2 to v3 100 | A few minor breaking changes were made in v3. Here's what you need to know: 101 | 102 | * You need to construct a `new Parser()` before calling `parseString` or `parseURL` 103 | * `parseFile` is no longer available (for better browser support) 104 | * `options` are now passed to the Parser constructor 105 | * `parsed.feed` is now just `feed` (top-level object removed) 106 | * `feed.entries` is now `feed.items` (to better match RSS XML) 107 | 108 | 109 | ## Output 110 | Check out the full output format in [test/output/reddit.json](test/output/reddit.json) 111 | 112 | ```yaml 113 | feedUrl: 'https://www.reddit.com/.rss' 114 | title: 'reddit: the front page of the internet' 115 | description: "" 116 | link: 'https://www.reddit.com/' 117 | items: 118 | - title: 'The water is too deep, so he improvises' 119 | link: 'https://www.reddit.com/r/funny/comments/3skxqc/the_water_is_too_deep_so_he_improvises/' 120 | pubDate: 'Thu, 12 Nov 2015 21:16:39 +0000' 121 | creator: "John Doe" 122 | content: 'this is a link & this is bold text' 123 | contentSnippet: 'this is a link & this is bold text' 124 | guid: 'https://www.reddit.com/r/funny/comments/3skxqc/the_water_is_too_deep_so_he_improvises/' 125 | categories: 126 | - funny 127 | isoDate: '2015-11-12T21:16:39.000Z' 128 | ``` 129 | 130 | ##### Notes: 131 | * The `contentSnippet` field strips out HTML tags and unescapes HTML entities 132 | * The `dc:` prefix will be removed from all fields 133 | * Both `dc:date` and `pubDate` will be available in ISO 8601 format as `isoDate` 134 | * If `author` is specified, but not `dc:creator`, `creator` will be set to `author` ([see article](http://www.lowter.com/blogs/2008/2/9/rss-dccreator-author)) 135 | * Atom's `updated` becomes `lastBuildDate` for consistency 136 | 137 | ## XML Options 138 | 139 | ### Custom Fields 140 | If your RSS feed contains fields that aren't currently returned, you can access them using the `customFields` option. 141 | 142 | ```js 143 | let parser = new Parser({ 144 | customFields: { 145 | feed: ['otherTitle', 'extendedDescription'], 146 | item: ['coAuthor','subtitle'], 147 | } 148 | }); 149 | 150 | parser.parseURL('https://www.reddit.com/.rss', function(err, feed) { 151 | console.log(feed.extendedDescription); 152 | 153 | feed.items.forEach(function(entry) { 154 | console.log(entry.coAuthor + ':' + entry.subtitle); 155 | }) 156 | }) 157 | ``` 158 | 159 | To rename fields, you can pass in an array with two items, in the format `[fromField, toField]`: 160 | 161 | ```js 162 | let parser = new Parser({ 163 | customFields: { 164 | item: [ 165 | ['dc:coAuthor', 'coAuthor'], 166 | ] 167 | } 168 | }) 169 | ``` 170 | 171 | To pass additional flags, provide an object as the third array item. Currently there is one such flag: 172 | 173 | * `keepArray (false)` - set to `true` to return *all* values for fields that can have multiple entries. 174 | * `includeSnippet (false)` - set to `true` to add an additional field, `${toField}Snippet`, with HTML stripped out 175 | 176 | ```js 177 | let parser = new Parser({ 178 | customFields: { 179 | item: [ 180 | ['media:content', 'media:content', {keepArray: true}], 181 | ] 182 | } 183 | }) 184 | ``` 185 | 186 | ### Default RSS version 187 | If your RSS Feed doesn't contain a `` tag with a `version` attribute, 188 | you can pass a `defaultRSS` option for the Parser to use: 189 | ```js 190 | let parser = new Parser({ 191 | defaultRSS: 2.0 192 | }); 193 | ``` 194 | 195 | 196 | ### xml2js passthrough 197 | `rss-parser` uses [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) 198 | to parse XML. You can pass [these options](https://github.com/Leonidas-from-XIV/node-xml2js#options) 199 | to `new xml2js.Parser()` by specifying `options.xml2js`: 200 | 201 | ```js 202 | let parser = new Parser({ 203 | xml2js: { 204 | emptyTag: '--EMPTY--', 205 | } 206 | }); 207 | ``` 208 | 209 | ## HTTP Options 210 | 211 | ### Timeout 212 | You can set the amount of time (in milliseconds) to wait before the HTTP request times out (default 60 seconds): 213 | 214 | ```js 215 | let parser = new Parser({ 216 | timeout: 1000, 217 | }); 218 | ``` 219 | 220 | ### Headers 221 | You can pass headers to the HTTP request: 222 | ```js 223 | let parser = new Parser({ 224 | headers: {'User-Agent': 'something different'}, 225 | }); 226 | ``` 227 | 228 | ### Redirects 229 | By default, `parseURL` will follow up to five redirects. You can change this 230 | with `options.maxRedirects`. 231 | 232 | ```js 233 | let parser = new Parser({maxRedirects: 100}); 234 | ``` 235 | 236 | ### Request passthrough 237 | `rss-parser` uses [http](https://nodejs.org/docs/latest/api/http.html#http_http_get_url_options_callback)/[https](https://nodejs.org/docs/latest/api/https.html#https_https_get_url_options_callback) module 238 | to do requests. You can pass [these options](https://nodejs.org/docs/latest/api/https.html#https_https_request_options_callback) 239 | to `http.get()`/`https.get()` by specifying `options.requestOptions`: 240 | 241 | e.g. to allow unauthorized certificate 242 | ```js 243 | let parser = new Parser({ 244 | requestOptions: { 245 | rejectUnauthorized: false 246 | } 247 | }); 248 | ``` 249 | 250 | ## Contributing 251 | Contributions are welcome! If you are adding a feature or fixing a bug, please be sure to add a [test case](https://github.com/bobby-brennan/rss-parser/tree/master/test/input) 252 | 253 | ### Running Tests 254 | The tests run the RSS parser for several sample RSS feeds in `test/input` and outputs the resulting JSON into `test/output`. If there are any changes to the output files the tests will fail. 255 | 256 | To check if your changes affect the output of any test cases, run 257 | 258 | `npm test` 259 | 260 | To update the output files with your changes, run 261 | 262 | `WRITE_GOLDEN=true npm test` 263 | 264 | ### Publishing Releases 265 | ```bash 266 | npm run build 267 | git commit -a -m "Build distribution" 268 | npm version minor # or major/patch 269 | npm publish 270 | git push --follow-tags 271 | ``` 272 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rss-parser", 3 | "description": "", 4 | "version": "1.1.0", 5 | "main": "dist/rss-parser.js", 6 | "authors": [ 7 | "Bobby Brennan" 8 | ], 9 | "license": "MIT", 10 | "homepage": "https://github.com/bobby-brennan/rss-parser", 11 | "moduleType": [ 12 | "node" 13 | ], 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'xml2js'; 2 | import { RequestOptions } from 'https'; 3 | 4 | declare namespace Parser { 5 | type CustomFieldItem = keyof U | (string | { keepArray: boolean })[] 6 | 7 | export interface CustomFields { 8 | readonly feed?: Array; 9 | readonly item?: CustomFieldItem[] | CustomFieldItem[][]; 10 | } 11 | 12 | export interface ParserOptions { 13 | readonly xml2js?: Options; 14 | readonly requestOptions?: RequestOptions; 15 | readonly headers?: Record; 16 | readonly defaultRSS?: number; 17 | readonly maxRedirects?: number; 18 | readonly customFields?: CustomFields; 19 | readonly timeout?: number; 20 | } 21 | 22 | export interface Enclosure { 23 | url: string; 24 | length?: number; 25 | type?: string; 26 | } 27 | 28 | export interface Item { 29 | link?: string; 30 | guid?: string; 31 | title?: string; 32 | pubDate?: string; 33 | creator?: string; 34 | summary?: string; 35 | content?: string; 36 | isoDate?: string; 37 | categories?: string[]; 38 | contentSnippet?: string; 39 | enclosure?: Enclosure; 40 | } 41 | 42 | export interface PaginationLinks { 43 | self?: string; 44 | first?: string; 45 | next?: string; 46 | last?: string; 47 | prev?: string; 48 | } 49 | 50 | export interface Output { 51 | image?: { 52 | link?: string; 53 | url: string; 54 | title?: string; 55 | }, 56 | paginationLinks?: PaginationLinks; 57 | link?: string; 58 | title?: string; 59 | items: (U & Item)[]; 60 | feedUrl?: string; 61 | description?: string; 62 | itunes?: { 63 | [key: string]: any; 64 | image?: string; 65 | owner?: { 66 | name?: string; 67 | email?: string; 68 | }; 69 | author?: string; 70 | summary?: string; 71 | explicit?: string; 72 | categories?: string[]; 73 | keywords?: string[]; 74 | }; 75 | } 76 | } 77 | 78 | /** 79 | * Class that handles all parsing or URL, or even XML, RSS feed to JSON. 80 | */ 81 | declare class Parser { 82 | /** 83 | * @param options - Parser options. 84 | */ 85 | constructor(options?: Parser.ParserOptions); 86 | /** 87 | * Parse XML content to JSON. 88 | * 89 | * @param xml - The xml to be parsed. 90 | * @param callback - Traditional callback. 91 | * 92 | * @returns Promise that has the same Output as the callback. 93 | */ 94 | parseString( 95 | xml: string, 96 | callback?: (err: Error, feed: Parser.Output) => void 97 | ): Promise>; 98 | 99 | /** 100 | * Parse URL content to JSON. 101 | * 102 | * @param feedUrl - The url that needs to be parsed to JSON. 103 | * @param callback - Traditional callback. 104 | * @param redirectCount - Max of redirects, default is set to five. 105 | * 106 | * @example 107 | * await parseURL('https://www.reddit.com/.rss'); 108 | * parseURL('https://www.reddit.com/.rss', (err, feed) => { ... }); 109 | * 110 | * @returns Promise that has the same Output as the callback. 111 | */ 112 | parseURL( 113 | feedUrl: string, 114 | callback?: (err: Error, feed: Parser.Output) => void, 115 | redirectCount?: number 116 | ): Promise>; 117 | } 118 | 119 | export = Parser; 120 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/parser'); 4 | 5 | -------------------------------------------------------------------------------- /lib/fields.js: -------------------------------------------------------------------------------- 1 | const fields = module.exports = {}; 2 | 3 | fields.feed = [ 4 | ['author', 'creator'], 5 | ['dc:publisher', 'publisher'], 6 | ['dc:creator', 'creator'], 7 | ['dc:source', 'source'], 8 | ['dc:title', 'title'], 9 | ['dc:type', 'type'], 10 | 'title', 11 | 'description', 12 | 'author', 13 | 'pubDate', 14 | 'webMaster', 15 | 'managingEditor', 16 | 'generator', 17 | 'link', 18 | 'language', 19 | 'copyright', 20 | 'lastBuildDate', 21 | 'docs', 22 | 'generator', 23 | 'ttl', 24 | 'rating', 25 | 'skipHours', 26 | 'skipDays', 27 | ]; 28 | 29 | fields.item = [ 30 | ['author', 'creator'], 31 | ['dc:creator', 'creator'], 32 | ['dc:date', 'date'], 33 | ['dc:language', 'language'], 34 | ['dc:rights', 'rights'], 35 | ['dc:source', 'source'], 36 | ['dc:title', 'title'], 37 | 'title', 38 | 'link', 39 | 'pubDate', 40 | 'author', 41 | 'summary', 42 | ['content:encoded', 'content:encoded', {includeSnippet: true}], 43 | 'enclosure', 44 | 'dc:creator', 45 | 'dc:date', 46 | 'comments', 47 | ]; 48 | 49 | var mapItunesField = function(f) { 50 | return ['itunes:' + f, f]; 51 | } 52 | 53 | fields.podcastFeed = ([ 54 | 'author', 55 | 'subtitle', 56 | 'summary', 57 | 'explicit' 58 | ]).map(mapItunesField); 59 | 60 | fields.podcastItem = ([ 61 | 'author', 62 | 'subtitle', 63 | 'summary', 64 | 'explicit', 65 | 'duration', 66 | 'image', 67 | 'episode', 68 | 'image', 69 | 'season', 70 | 'keywords', 71 | 'episodeType' 72 | ]).map(mapItunesField); 73 | 74 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const http = require('http'); 3 | const https = require('https'); 4 | const xml2js = require('xml2js'); 5 | const url = require('url'); 6 | 7 | const fields = require('./fields'); 8 | const utils = require('./utils'); 9 | 10 | const DEFAULT_HEADERS = { 11 | 'User-Agent': 'rss-parser', 12 | 'Accept': 'application/rss+xml', 13 | } 14 | const DEFAULT_MAX_REDIRECTS = 5; 15 | const DEFAULT_TIMEOUT = 60000; 16 | 17 | class Parser { 18 | constructor(options={}) { 19 | options.headers = options.headers || {}; 20 | options.xml2js = options.xml2js || {}; 21 | options.customFields = options.customFields || {}; 22 | options.customFields.item = options.customFields.item || []; 23 | options.customFields.feed = options.customFields.feed || []; 24 | options.requestOptions = options.requestOptions || {}; 25 | if (!options.maxRedirects) options.maxRedirects = DEFAULT_MAX_REDIRECTS; 26 | if (!options.timeout) options.timeout = DEFAULT_TIMEOUT; 27 | this.options = options; 28 | this.xmlParser = new xml2js.Parser(this.options.xml2js); 29 | this.etags = {}; 30 | this.lastModified = {}; 31 | } 32 | 33 | parseString(xml, callback) { 34 | let prom = new Promise((resolve, reject) => { 35 | this.xmlParser.parseString(xml, (err, result) => { 36 | if (err) return reject(err); 37 | if (!result) { 38 | return reject(new Error('Unable to parse XML.')); 39 | } 40 | let feed = null; 41 | if (result.feed) { 42 | feed = this.buildAtomFeed(result); 43 | } else if (result.rss && result.rss.$ && result.rss.$.version && result.rss.$.version.match(/^2/)) { 44 | feed = this.buildRSS2(result); 45 | } else if (result['rdf:RDF']) { 46 | feed = this.buildRSS1(result); 47 | } else if (result.rss && result.rss.$ && result.rss.$.version && result.rss.$.version.match(/0\.9/)) { 48 | feed = this.buildRSS0_9(result); 49 | } else if (result.rss && this.options.defaultRSS) { 50 | switch(this.options.defaultRSS) { 51 | case 0.9: 52 | feed = this.buildRSS0_9(result); 53 | break; 54 | case 1: 55 | feed = this.buildRSS1(result); 56 | break; 57 | case 2: 58 | feed = this.buildRSS2(result); 59 | break; 60 | default: 61 | return reject(new Error("default RSS version not recognized.")) 62 | } 63 | } else { 64 | return reject(new Error("Feed not recognized as RSS 1 or 2.")) 65 | } 66 | resolve(feed); 67 | }); 68 | }); 69 | prom = utils.maybePromisify(callback, prom); 70 | return prom; 71 | } 72 | 73 | parseURL(feedUrl, callback, redirectCount=0) { 74 | let xml = ''; 75 | let get = feedUrl.indexOf('https') === 0 ? https.get : http.get; 76 | let urlParts = url.parse(feedUrl); 77 | let headers = Object.assign({}, DEFAULT_HEADERS, this.options.headers); 78 | if (this.etags[feedUrl]) { 79 | headers['If-None-Match'] = this.etags[feedUrl]; 80 | } 81 | if (this.lastModified[feedUrl]) { 82 | headers['If-Modified-Since'] = this.lastModified[feedUrl]; 83 | } 84 | let timeout = null; 85 | let prom = new Promise((resolve, reject) => { 86 | const requestOpts = Object.assign({headers}, urlParts, this.options.requestOptions); 87 | let req = get(requestOpts, (res) => { 88 | if (this.options.maxRedirects && res.statusCode >= 300 && res.statusCode < 400 && res.headers['location']) { 89 | if (redirectCount === this.options.maxRedirects) { 90 | return reject(new Error("Too many redirects")); 91 | } else { 92 | const newLocation = url.resolve(feedUrl, res.headers['location']); 93 | return this.parseURL(newLocation, null, redirectCount + 1).then(resolve, reject); 94 | } 95 | } else if (res.statusCode === 304) { 96 | return resolve(null); 97 | } 98 | else if (res.statusCode >= 300) { 99 | return reject(new Error("Status code " + res.statusCode)); 100 | } 101 | 102 | if (res.headers['etag']) { 103 | this.etags[feedUrl] = res.headers['etag']; 104 | } 105 | 106 | if (res.headers['last-modified']) { 107 | this.lastModified[feedUrl] = res.headers['last-modified']; 108 | } 109 | 110 | let encoding = utils.getEncodingFromContentType(res.headers['content-type']); 111 | res.setEncoding(encoding); 112 | res.on('data', (chunk) => { 113 | xml += chunk; 114 | }); 115 | res.on('end', () => { 116 | return this.parseString(xml).then(resolve, reject); 117 | }); 118 | }) 119 | req.on('error', reject); 120 | timeout = setTimeout(() => { 121 | let err = new Error("Request timed out after " + this.options.timeout + "ms"); 122 | req.destroy(err); 123 | reject(err); 124 | }, this.options.timeout); 125 | }).then(data => { 126 | clearTimeout(timeout); 127 | return Promise.resolve(data); 128 | }, e => { 129 | clearTimeout(timeout); 130 | return Promise.reject(e); 131 | }); 132 | prom = utils.maybePromisify(callback, prom); 133 | return prom; 134 | } 135 | 136 | buildAtomFeed(xmlObj) { 137 | let feed = {items: []}; 138 | utils.copyFromXML(xmlObj.feed, feed, this.options.customFields.feed); 139 | if (xmlObj.feed.link) { 140 | feed.link = utils.getLink(xmlObj.feed.link, 'alternate', 0); 141 | feed.feedUrl = utils.getLink(xmlObj.feed.link, 'self', 1); 142 | } 143 | if (xmlObj.feed.title) { 144 | let title = xmlObj.feed.title[0] || ''; 145 | if (title._) title = title._ 146 | if (title) feed.title = title; 147 | } 148 | if (xmlObj.feed.updated) { 149 | feed.lastBuildDate = xmlObj.feed.updated[0]; 150 | } 151 | feed.items = (xmlObj.feed.entry || []).map(entry => this.parseItemAtom(entry)); 152 | return feed; 153 | } 154 | 155 | parseItemAtom(entry) { 156 | let item = {}; 157 | utils.copyFromXML(entry, item, this.options.customFields.item); 158 | if (entry.title) { 159 | let title = entry.title[0] || ''; 160 | if (title._) title = title._; 161 | if (title) item.title = title; 162 | } 163 | if (entry.link && entry.link.length) { 164 | item.link = utils.getLink(entry.link, 'alternate', 0); 165 | } 166 | if (entry.published && entry.published.length && entry.published[0].length) item.pubDate = new Date(entry.published[0]).toISOString(); 167 | if (!item.pubDate && entry.updated && entry.updated.length && entry.updated[0].length) item.pubDate = new Date(entry.updated[0]).toISOString(); 168 | if (entry.author && entry.author.length && entry.author[0].name && entry.author[0].name.length) item.author = entry.author[0].name[0]; 169 | if (entry.content && entry.content.length) { 170 | item.content = utils.getContent(entry.content[0]); 171 | item.contentSnippet = utils.getSnippet(item.content) 172 | } 173 | if (entry.summary && entry.summary.length) { 174 | item.summary = utils.getContent(entry.summary[0]); 175 | } 176 | if (entry.id) { 177 | item.id = entry.id[0]; 178 | } 179 | this.setISODate(item); 180 | return item; 181 | } 182 | 183 | buildRSS0_9(xmlObj) { 184 | var channel = xmlObj.rss.channel[0]; 185 | var items = channel.item; 186 | return this.buildRSS(channel, items); 187 | } 188 | 189 | buildRSS1(xmlObj) { 190 | xmlObj = xmlObj['rdf:RDF']; 191 | let channel = xmlObj.channel[0]; 192 | let items = xmlObj.item; 193 | return this.buildRSS(channel, items); 194 | } 195 | 196 | buildRSS2(xmlObj) { 197 | let channel = xmlObj.rss.channel[0]; 198 | let items = channel.item; 199 | let feed = this.buildRSS(channel, items); 200 | if (xmlObj.rss.$ && xmlObj.rss.$['xmlns:itunes']) { 201 | this.decorateItunes(feed, channel); 202 | } 203 | return feed; 204 | } 205 | 206 | buildRSS(channel, items) { 207 | items = items || []; 208 | let feed = {items: []}; 209 | let feedFields = fields.feed.concat(this.options.customFields.feed); 210 | let itemFields = fields.item.concat(this.options.customFields.item); 211 | if (channel['atom:link'] && channel['atom:link'][0] && channel['atom:link'][0].$) { 212 | feed.feedUrl = channel['atom:link'][0].$.href; 213 | } 214 | if (channel.image && channel.image[0] && channel.image[0].url) { 215 | feed.image = {}; 216 | let image = channel.image[0]; 217 | if (image.link) feed.image.link = image.link[0]; 218 | if (image.url) feed.image.url = image.url[0]; 219 | if (image.title) feed.image.title = image.title[0]; 220 | if (image.width) feed.image.width = image.width[0]; 221 | if (image.height) feed.image.height = image.height[0]; 222 | } 223 | const paginationLinks = this.generatePaginationLinks(channel); 224 | if (Object.keys(paginationLinks).length) { 225 | feed.paginationLinks = paginationLinks; 226 | } 227 | utils.copyFromXML(channel, feed, feedFields); 228 | feed.items = items.map(xmlItem => this.parseItemRss(xmlItem, itemFields)); 229 | return feed; 230 | } 231 | 232 | parseItemRss(xmlItem, itemFields) { 233 | let item = {}; 234 | utils.copyFromXML(xmlItem, item, itemFields); 235 | if (xmlItem.enclosure) { 236 | item.enclosure = xmlItem.enclosure[0].$; 237 | } 238 | if (xmlItem.description) { 239 | item.content = utils.getContent(xmlItem.description[0]); 240 | item.contentSnippet = utils.getSnippet(item.content); 241 | } 242 | if (xmlItem.guid) { 243 | item.guid = xmlItem.guid[0]; 244 | if (item.guid._) item.guid = item.guid._; 245 | } 246 | if (xmlItem.$ && xmlItem.$['rdf:about']) { 247 | item['rdf:about'] = xmlItem.$['rdf:about'] 248 | } 249 | if (xmlItem.category) item.categories = xmlItem.category; 250 | 251 | var mediaContent = xmlItem['media:content']?.[0]?.$ ?? null; 252 | if(mediaContent) item.mediaContent = mediaContent; 253 | 254 | this.setISODate(item); 255 | return item; 256 | } 257 | 258 | /** 259 | * Add iTunes specific fields from XML to extracted JSON 260 | * 261 | * @access public 262 | * @param {object} feed extracted 263 | * @param {object} channel parsed XML 264 | */ 265 | decorateItunes(feed, channel) { 266 | let items = channel.item || []; 267 | let categories = []; 268 | feed.itunes = {} 269 | 270 | if (channel['itunes:owner']) { 271 | let owner = {}; 272 | 273 | if(channel['itunes:owner'][0]['itunes:name']) { 274 | owner.name = channel['itunes:owner'][0]['itunes:name'][0]; 275 | } 276 | if(channel['itunes:owner'][0]['itunes:email']) { 277 | owner.email = channel['itunes:owner'][0]['itunes:email'][0]; 278 | } 279 | feed.itunes.owner = owner; 280 | } 281 | 282 | if (channel['itunes:image']) { 283 | let image; 284 | let hasImageHref = (channel['itunes:image'][0] && 285 | channel['itunes:image'][0].$ && 286 | channel['itunes:image'][0].$.href); 287 | image = hasImageHref ? channel['itunes:image'][0].$.href : null; 288 | if (image) { 289 | feed.itunes.image = image; 290 | } 291 | } 292 | 293 | if (channel['itunes:category']) { 294 | const categoriesWithSubs = channel['itunes:category'].map((category) => { 295 | return { 296 | name: category && category.$ && category.$.text, 297 | subs: category['itunes:category'] ? 298 | category['itunes:category'] 299 | .map((subcategory) => ({ 300 | name: subcategory && subcategory.$ && subcategory.$.text 301 | })) : null, 302 | }; 303 | }); 304 | 305 | feed.itunes.categories = categoriesWithSubs.map((category) => category.name); 306 | feed.itunes.categoriesWithSubs = categoriesWithSubs; 307 | } 308 | 309 | if (channel['itunes:keywords']) { 310 | if (channel['itunes:keywords'].length > 1) { 311 | feed.itunes.keywords = channel['itunes:keywords'].map( 312 | keyword => keyword && keyword.$ && keyword.$.text 313 | ); 314 | } else { 315 | let keywords = channel['itunes:keywords'][0]; 316 | if (keywords && typeof keywords._ === 'string') { 317 | keywords = keywords._; 318 | } 319 | 320 | if (keywords && keywords.$ && keywords.$.text) { 321 | feed.itunes.keywords = keywords.$.text.split(',') 322 | } else if (typeof keywords === "string") { 323 | feed.itunes.keywords = keywords.split(','); 324 | } 325 | } 326 | } 327 | 328 | utils.copyFromXML(channel, feed.itunes, fields.podcastFeed); 329 | items.forEach((item, index) => { 330 | let entry = feed.items[index]; 331 | entry.itunes = {}; 332 | utils.copyFromXML(item, entry.itunes, fields.podcastItem); 333 | let image = item['itunes:image']; 334 | if (image && image[0] && image[0].$ && image[0].$.href) { 335 | entry.itunes.image = image[0].$.href; 336 | } 337 | }); 338 | } 339 | 340 | setISODate(item) { 341 | let date = item.pubDate || item.date; 342 | if (date) { 343 | try { 344 | item.isoDate = new Date(date.trim()).toISOString(); 345 | } catch (e) { 346 | // Ignore bad date format 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * Generates a pagination object where the rel attribute is the key and href attribute is the value 353 | * { self: 'self-url', first: 'first-url', ... } 354 | * 355 | * @access private 356 | * @param {Object} channel parsed XML 357 | * @returns {Object} 358 | */ 359 | generatePaginationLinks(channel) { 360 | if (!channel['atom:link']) { 361 | return {}; 362 | } 363 | const paginationRelAttributes = ['self', 'first', 'next', 'prev', 'last']; 364 | 365 | return channel['atom:link'].reduce((paginationLinks, link) => { 366 | if (!link.$ || !paginationRelAttributes.includes(link.$.rel)) { 367 | return paginationLinks; 368 | } 369 | paginationLinks[link.$.rel] = link.$.href; 370 | return paginationLinks; 371 | }, {}); 372 | } 373 | } 374 | 375 | module.exports = Parser; 376 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const utils = module.exports = {}; 2 | const entities = require('entities'); 3 | const xml2js = require('xml2js'); 4 | 5 | utils.stripHtml = function(str) { 6 | str = str.replace(/([^\n])<\/?(h|br|p|ul|ol|li|blockquote|section|table|tr|div)(?:.|\n)*?>([^\n])/gm, '$1\n$3') 7 | str = str.replace(/<(?:.|\n)*?>/gm, ''); 8 | return str; 9 | } 10 | 11 | utils.getSnippet = function(str) { 12 | return entities.decodeHTML(utils.stripHtml(str)).trim(); 13 | } 14 | 15 | utils.getLink = function(links, rel, fallbackIdx) { 16 | if (!links) return; 17 | for (let i = 0; i < links.length; ++i) { 18 | if (links[i].$.rel === rel) return links[i].$.href; 19 | } 20 | if (links[fallbackIdx]) return links[fallbackIdx].$.href; 21 | } 22 | 23 | utils.getContent = function(content) { 24 | if (typeof content._ === 'string') { 25 | return content._; 26 | } else if (typeof content === 'object') { 27 | let builder = new xml2js.Builder({headless: true, explicitRoot: true, rootName: 'div', renderOpts: {pretty: false}}); 28 | return builder.buildObject(content); 29 | } else { 30 | return content; 31 | } 32 | } 33 | 34 | utils.copyFromXML = function(xml, dest, fields) { 35 | fields.forEach(function(f) { 36 | let from = f; 37 | let to = f; 38 | let options = {}; 39 | if (Array.isArray(f)) { 40 | from = f[0]; 41 | to = f[1]; 42 | if (f.length > 2) { 43 | options = f[2]; 44 | } 45 | } 46 | const { keepArray, includeSnippet } = options; 47 | if (xml[from] !== undefined){ 48 | dest[to] = keepArray ? xml[from] : xml[from][0]; 49 | } 50 | if (dest[to] && typeof dest[to]._ === 'string') { 51 | dest[to]=dest[to]._; 52 | } 53 | if (includeSnippet && dest[to] && typeof dest[to] === 'string') { 54 | dest[to + 'Snippet'] = utils.getSnippet(dest[to]); 55 | } 56 | }) 57 | } 58 | 59 | utils.maybePromisify = function(callback, promise) { 60 | if (!callback) return promise; 61 | return promise.then( 62 | data => setTimeout(() => callback(null, data)), 63 | err => setTimeout(() => callback(err)) 64 | ); 65 | } 66 | 67 | const DEFAULT_ENCODING = 'utf8'; 68 | const ENCODING_REGEX = /(encoding|charset)\s*=\s*(\S+)/; 69 | const SUPPORTED_ENCODINGS = ['ascii', 'utf8', 'utf16le', 'ucs2', 'base64', 'latin1', 'binary', 'hex']; 70 | const ENCODING_ALIASES = { 71 | 'utf-8': 'utf8', 72 | 'iso-8859-1': 'latin1', 73 | } 74 | 75 | utils.getEncodingFromContentType = function(contentType) { 76 | contentType = contentType || ''; 77 | let match = contentType.match(ENCODING_REGEX); 78 | let encoding = (match || [])[2] || ''; 79 | encoding = encoding.toLowerCase(); 80 | encoding = ENCODING_ALIASES[encoding] || encoding; 81 | if (!encoding || SUPPORTED_ENCODINGS.indexOf(encoding) === -1) { 82 | encoding = DEFAULT_ENCODING; 83 | } 84 | return encoding; 85 | } 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rss-parser", 3 | "version": "3.13.0", 4 | "main": "index.js", 5 | "types": "index.d.ts", 6 | "scripts": { 7 | "test": "mocha --reporter-option maxDiffSize=0 --exit", 8 | "build": "./scripts/build.sh" 9 | }, 10 | "author": "Bobby Brennan", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@babel/core": "^7.21.4", 14 | "@babel/preset-env": "^7.21.4", 15 | "@types/xml2js": "^0.4.3", 16 | "babel-core": "^6.26.3", 17 | "babel-loader": "^8.0.4", 18 | "babel-preset-env": "^1.7.0", 19 | "chai": "^3.4.1", 20 | "express": "^4.16.3", 21 | "mocha": "^10.2.0", 22 | "puppeteer": "^5.5.0" 23 | }, 24 | "dependencies": { 25 | "entities": "^2.0.3", 26 | "webpack": "^5.90.1", 27 | "webpack-cli": "^5.1.4", 28 | "xml2js": "^0.5.0" 29 | }, 30 | "directories": { 31 | "test": "test" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/bobby-brennan/rss-parser.git" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/bobby-brennan/rss-parser/issues" 39 | }, 40 | "homepage": "https://github.com/bobby-brennan/rss-parser#readme", 41 | "description": "A lightweight RSS parser, for Node and the browser", 42 | "keywords": [ 43 | "RSS", 44 | "RSS to JSON", 45 | "RSS reader", 46 | "RSS parser", 47 | "RSS to JS", 48 | "Feed reader" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | echo "Building dev..." 3 | webpack-cli --mode=development --target=web 4 | echo "Building prod..." 5 | webpack-cli --mode=production --target=web --output-filename=dist/[name].min.js --profile --json > dist/stats.json 6 | 7 | -------------------------------------------------------------------------------- /test/browser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const express = require('express'); 4 | const expect = require('chai').expect; 5 | const Browser = require('puppeteer'); 6 | 7 | let browser = null; 8 | let page = null; 9 | 10 | const PORT = 3333; 11 | const PARSE_TIMEOUT = 1000; 12 | 13 | describe('Browser', function() { 14 | if (process.env.SKIP_BROWSER_TESTS) { 15 | console.log('skipping browser tests'); 16 | return; 17 | } 18 | 19 | before(function(done) { 20 | this.timeout(5000); 21 | let app = express(); 22 | app.use(express.static(__dirname)); 23 | app.use('/dist', express.static(__dirname + '/../dist')); 24 | app.listen(PORT, err => { 25 | if (err) return done(err); 26 | Browser.launch({args: ['--no-sandbox']}) 27 | .then(b => browser = b) 28 | .then(_ => browser.newPage()) 29 | .then(p => { 30 | page = p; 31 | return page.goto('http://localhost:3333/index.html'); 32 | }) 33 | .then(_ => done()) 34 | .catch(e => done(e)); 35 | }); 36 | }); 37 | 38 | after(() => browser.close()); 39 | 40 | it('should have window.RSSParser', () => { 41 | return page.evaluate(() => { 42 | return typeof window.RSSParser; 43 | }).then(type => { 44 | expect(type).to.equal('function'); 45 | }) 46 | }); 47 | 48 | it('should parse reddit', function() { 49 | this.timeout(PARSE_TIMEOUT + 1000); 50 | return page.evaluate(() => { 51 | var parser = new RSSParser(); 52 | parser.parseURL('http://localhost:3333/input/reddit.rss', function(err, data) { 53 | window.error = err; 54 | window.reddit = data; 55 | }) 56 | }) 57 | .then(_ => { 58 | return new Promise(resolve => setTimeout(resolve, PARSE_TIMEOUT)) 59 | }) 60 | .then(_ => page.evaluate(() => { 61 | return window.error; 62 | })) 63 | .then(err => { 64 | expect(err).to.equal(null); 65 | }) 66 | .then(_ => page.evaluate(() => { 67 | return window.reddit.title; 68 | })) 69 | .then(title => { 70 | expect(title).to.equal('reddit: the front page of the internet'); 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var HTTP = require('http'); 5 | 6 | var utils = require('../lib/utils.js'); 7 | 8 | var Expect = require('chai').expect; 9 | 10 | var IN_DIR = __dirname + '/input'; 11 | var OUT_DIR = __dirname + '/output'; 12 | 13 | describe('Utils', function() { 14 | it('should strip HTML appropriately', () => { 15 | var testCases = [{ 16 | input: 'Hello world', 17 | output: 'Hello world', 18 | }, { 19 | input: '

hi

my name is', 20 | output: 'hi\nmy name is', 21 | }, { 22 | input: 'hello
world', 23 | output: 'hello\nworld', 24 | }, { 25 | input: 'hello
world', 26 | output: 'hello\nworld', 27 | }, { 28 | input: 'hi

my name is

', 29 | output: 'hi\nmy name is', 30 | }, { 31 | input: 'hello

world

my

name is

', 32 | output: 'hello\nworld\nmy\nname is', 33 | }, { 34 | input: 'hellomy name is', 35 | output: 'hellomy name is', 36 | }, { 37 | input: 'hello my name is >', 38 | output: 'hello my name is >', 39 | }] 40 | testCases.forEach(tc => { 41 | Expect('|' + utils.getSnippet(tc.input) + '|').to.equal('|' + tc.output + '|', tc.input); 42 | }); 43 | }) 44 | }); 45 | 46 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/input/encoding.rss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbren/rss-parser/e9b21691d25cc125c7a4d7be9835e318aee5bc70/test/input/encoding.rss -------------------------------------------------------------------------------- /test/input/gulp-atom.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | tag:github.com,2008:https://github.com/gulpjs/gulp/releases 4 | 5 | 6 | Release notes from gulp 7 | 2015-06-01T23:49:41+02:00 8 | 9 | tag:github.com,2008:Repository/11167738/v3.9.0 10 | 2015-06-01T23:49:41+02:00 11 | 12 | v3.9.0 13 | <p>3.9.0</p> 14 | 15 | contra 16 | 17 | 18 | 19 | 20 | tag:github.com,2008:Repository/11167738/v3.8.11 21 | 2015-02-09T21:37:28+01:00 22 | 23 | v3.8.11 24 | <p>3.8.11</p> 25 | 26 | contra 27 | 28 | 29 | 30 | 31 | tag:github.com,2008:Repository/11167738/v3.8.10 32 | 2014-11-04T01:11:30+01:00 33 | 34 | v3.8.10 35 | <p>3.8.10</p> 36 | 37 | contra 38 | 39 | 40 | 41 | 42 | tag:github.com,2008:Repository/11167738/v3.8.9 43 | 2014-10-22T08:55:20+02:00 44 | 45 | v3.8.9 46 | <p>3.8.9</p> 47 | 48 | contra 49 | 50 | 51 | 52 | 53 | tag:github.com,2008:Repository/11167738/v3.8.8 54 | 2014-09-07T22:20:11+02:00 55 | 56 | v3.8.8 57 | <p>3.8.8</p> 58 | 59 | contra 60 | 61 | 62 | 63 | 64 | tag:github.com,2008:Repository/11167738/v3.8.7 65 | 2014-08-02T06:58:19+02:00 66 | 67 | v3.8.7 68 | <p>3.8.7</p> 69 | 70 | contra 71 | 72 | 73 | 74 | 75 | tag:github.com,2008:Repository/11167738/v3.8.6 76 | 2014-07-10T00:06:50+02:00 77 | 78 | v3.8.6 79 | <p>3.8.6</p> 80 | 81 | contra 82 | 83 | 84 | 85 | 86 | tag:github.com,2008:Repository/11167738/v3.8.5 87 | 2014-06-27T08:53:54+02:00 88 | 89 | v3.8.5 90 | <p>3.8.5</p> 91 | 92 | contra 93 | 94 | 95 | 96 | 97 | tag:github.com,2008:Repository/11167738/v3.8.4 98 | 2014-06-27T08:38:43+02:00 99 | 100 | v3.8.4 101 | <p>3.8.4</p> 102 | 103 | contra 104 | 105 | 106 | 107 | 108 | tag:github.com,2008:Repository/11167738/v3.8.3 109 | 2014-06-26T23:17:51+02:00 110 | 111 | v3.8.3 112 | <p>3.8.3</p> 113 | 114 | contra 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /test/input/heise.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | heise developer neueste Meldungen 4 | Informationen für Entwickler 5 | 2016-02-01T17:54:50+01:00 6 | http://www.heise.de/developer/http://www.heise.de/developer/icons/heise_developer_logo_weiss.gif 7 | 8 | 9 | Copyright (c) 2016 Heise Medien 10 | 11 | heise online 12 | 13 | 14 | 15 | Java-Anwendungsserver: Red Hat gibt WildFly 10 frei 16 | http://heise.de/-3088438 17 | 2016-02-01T17:54:50+01:00 18 | 2016-02-01T17:22:00+01:00 19 | 20 | Die nun verfügbare Version 10 des Enterprise-Java-Servers stellt die Basis für Red Hats kommerzielle JBoss Enterprise Application Platform 7 ist zugleich das dritte größere Release seit dem Namenswechsel des Open-Source-Projekts. 21 | 23 | 24 | WildFly 10 25 | 26 | 27 |

Die nun verfügbare Version 10 des Enterprise-Java-Servers stellt die Basis für Red Hats kommerzielle JBoss Enterprise Application Platform 7 ist zugleich das dritte größere Release seit dem Namenswechsel des Open-Source-Projekts.

28 | ]]>
29 |
30 | 31 | Scrum Day 2016: Bewerbungen für Vorträge und Workshops 32 | http://heise.de/-3088627 33 | 2016-02-01T14:29:00+01:00 34 | 2016-02-01T14:29:00+01:00 35 | 36 | Über das Programm des zehnten Scrum Day stimmen die Teilnehmer selbst ab. Doch zuvor sind Scrum-Experten dran, sich bis Ende Februar mit einem Vortrag oder Workshop zu bewerben. 37 | 39 | 40 | Vortragseinreichungen zum Scrum Day 2016 erwünscht 41 | 42 | 43 |

Über das Programm des zehnten Scrum Day stimmen die Teilnehmer selbst ab. Doch zuvor sind Scrum-Experten dran, sich bis Ende Februar mit einem Vortrag oder Workshop zu bewerben.

44 | ]]>
45 |
46 | 47 | Microsoft veröffentlicht Cordova-Erweiterung für Visual Studio Code 48 | http://heise.de/-3088372 49 | 2016-02-01T12:12:11+01:00 50 | 2016-02-01T12:12:00+01:00 51 | 52 | Mit dem für unterschiedliche Plattformen verfügbaren Code-Editor lassen sich nun auch hybride Apps für iOS, Android, Blackberry und Windows Phone auf Basis von HTML, CSS und JavaScript entwickeln. 53 | 55 | 56 | Microsoft veröffentlicht Cordova-Erweiterung für Visual Studio Code 57 | 58 | 59 |

Mit dem für unterschiedliche Plattformen verfügbaren Code-Editor lassen sich nun auch hybride Apps für iOS, Android, Blackberry und Windows Phone auf Basis von HTML, CSS und JavaScript entwickeln.

60 | ]]>
61 |
62 | 63 | Ungewisse Zukunft des MySQLDumper-Projekts 64 | http://heise.de/-3088410 65 | 2016-02-01T10:34:18+01:00 66 | 2016-02-01T10:34:00+01:00 67 | 68 | Daniel Schlichtholz hat sich von der Entwicklung des PHP- beziehungsweise Perl-Skripts verabschiedet, mit dem sich MySQL-Daten sichern und gegebenenfalls wiederherstellen lassen. Fällige Anpassungen an das neue PHP 7 würden ihn zu viel Zeit kosten. 69 | 71 | 72 | Ungewisse Zukunft des MySQLDumper-Projekts 73 | 74 | 75 |

Daniel Schlichtholz hat sich von der Entwicklung des PHP- beziehungsweise Perl-Skripts verabschiedet, mit dem sich MySQL-Daten sichern und gegebenenfalls wiederherstellen lassen. Fällige Anpassungen an das neue PHP 7 würden ihn zu viel Zeit kosten.

76 | ]]>
77 |
78 | 79 | Änderungen bei der Authentifizierung in Microsofts v2.0 App Model 80 | http://heise.de/-3088319 81 | 2016-02-01T10:19:01+01:00 82 | 2016-02-01T10:19:00+01:00 83 | 84 | Microsoft ändert das v2.0 Auth Protocol zur Anmeldung an die Cloud-Dienste Microsoft Account und Azure Active Directory. Entwickler müssen aufgrund der Neuerungen vermutlich vorhandenen Code anpassen. 85 | 87 | 88 | Änderungen in der Authentifizierung an Microsofts v2.0 App Model 89 | 90 | 91 |

Microsoft ändert das v2.0 Auth Protocol zur Anmeldung an die Cloud-Dienste Microsoft Account und Azure Active Directory. Entwickler müssen aufgrund der Neuerungen vermutlich vorhandenen Code anpassen.

92 | ]]>
93 |
94 | 95 | Der Pragmatische Architekt: Ein gutes Szenario 96 | http://heise.de/-3088146 97 | 2016-02-01T08:24:19+01:00 98 | 2016-02-01T08:24:00+01:00 99 | 100 | Wie entwirft ein Softwarearchitekt am besten ein System? Dass es dafür kein einfaches Rezept gibt, ist der Komplexität und Heterogenität von Softwareprojekten geschuldet. Die Sache ist aber nicht hoffnungslos. Szenarien bieten für diesen Zweck ein sehr gutes Instrumentarium. 101 | Wie entwirft ein Softwarearchitekt am besten ein System? Dass es dafür kein einfaches Rezept gibt, ist der Komplexität und Heterogenität von Softwareprojekten geschuldet. Die Sache ist aber nicht hoffnungslos. Szenarien bieten für diesen Zweck ein sehr gutes Instrumentarium.

103 | ]]>
104 |
105 | 106 | Developer Snapshots: Programmierer-News in ein, zwei Sätzen 107 | http://heise.de/-3087585 108 | 2016-01-29T15:37:06+01:00 109 | 2016-01-29T15:37:00+01:00 110 | 111 | heise Developer fasst jede Woche bisher vernachlässigte, aber doch wichtige Nachrichten zu Tools, Spezifikationen oder anderem zusammen – dieses Mal u.a. mit einem OCaml-Cross-Compiler für iOS, LLVM 3.8 und Cloud9-Integration in Googles Cloud-Plattform. 112 | 114 | 115 | Developer Snapshots: Programmierer-News in ein, zwei Sätzen 116 | 117 | 118 |

heise Developer fasst jede Woche bisher vernachlässigte, aber doch wichtige Nachrichten zu Tools, Spezifikationen oder anderem zusammen – dieses Mal u.a. mit einem OCaml-Cross-Compiler für iOS, LLVM 3.8 und Cloud9-Integration in Googles Cloud-Plattform.

119 | ]]>
120 |
121 | 122 | Der Dotnet-Doktor: Auslesen und Sortieren von GPX-Dateien 123 | http://heise.de/-3087150 124 | 2016-01-29T13:59:32+01:00 125 | 2016-01-29T13:59:00+01:00 126 | 127 | Die Windows PowerShell biete eine schnelle Lösung für die Aufgabe, eine größere Menge von XML-Dateien zu sortieren. 128 | Die Windows PowerShell biete eine schnelle Lösung für die Aufgabe, eine größere Menge von XML-Dateien zu sortieren.

130 | ]]>
131 |
132 | 133 | SourceForge und Slashdot wechseln erneut den Besitzer 134 | http://heise.de/-3087034 135 | 2016-01-29T14:24:17+01:00 136 | 2016-01-29T13:02:00+01:00 137 | 138 | Der neue Besitzer, ein US-amerikanisches Webmedia-Unternehmen, will offenbar den ramponierten Ruf der Hosting-Plattform für Open-Source-Projekte aufpolieren. 139 | 141 | 142 | SourceForge und Slashdot bekommen neuen Besitzer 143 | 144 | 145 |

Der neue Besitzer, ein US-amerikanisches Webmedia-Unternehmen, will offenbar den ramponierten Ruf der Hosting-Plattform für Open-Source-Projekte aufpolieren.

146 | ]]>
147 |
148 | 149 | Tales from the Web side: Patterns und Best Practices in JavaScript: Typüberprüfungen continued 150 | http://heise.de/-3086830 151 | 2016-01-29T11:46:59+01:00 152 | 2016-01-29T11:23:00+01:00 153 | 154 | Der instanceof-Operator in JavaScript kann in einigen Fällen problematisch sein kann, nämlich immer dann, wenn man mit mehreren Versionen der gleichen &quot;Klasse&quot; arbeitet. Nun werden einige Lösungsansätze skizziert. 155 | Der instanceof-Operator in JavaScript kann in einigen Fällen problematisch sein kann, nämlich immer dann, wenn man mit mehreren Versionen der gleichen "Klasse" arbeitet. Nun werden einige Lösungsansätze skizziert.

157 | ]]>
158 |
159 | 160 | Facebook schließt Parse-Plattform 161 | http://heise.de/-3086857 162 | 2016-01-29T12:53:33+01:00 163 | 2016-01-29T10:12:00+01:00 164 | 165 | Ein Jahr bleibt Entwicklern, die den Mobile Backend as a Service nutzen, auf ein eigenes MongoDB-basiertes Angebot zu migrieren oder den nun als Open Source verfügbaren Parse-Server zu verwenden. 166 | 168 | 169 | Facebook kündigt Schließung der Parse-Plattform an 170 | 171 | 172 |

Ein Jahr bleibt Entwicklern, die den Mobile Backend as a Service nutzen, auf ein eigenes MongoDB-basiertes Angebot zu migrieren oder den nun als Open Source verfügbaren Parse-Server zu verwenden.

173 | ]]>
174 |
175 | 176 | Continuous Lifecycle London: Programm online, Ticketverkauf gestartet 177 | http://heise.de/-3086901 178 | 2016-01-29T10:10:17+01:00 179 | 2016-01-29T10:10:00+01:00 180 | 181 | Anfang Mai feiert die englische Version der Continuous-Lifecycle-Konferenz in London Premiere. Jez Humble und Dave Farley sind nur zwei der Sprecher aus dem nun bekannt gegebenen Programm, das Continuous Delivery, DevOps und Co. ins Zentrum stellt. 182 | 184 | 185 | Continuous Lifecycle London: Programm online, Ticketverkauf gestartet 186 | 187 | 188 |

Anfang Mai feiert die englische Version der Continuous-Lifecycle-Konferenz in London Premiere. Jez Humble und Dave Farley sind nur zwei der Sprecher aus dem nun bekannt gegebenen Programm, das Continuous Delivery, DevOps und Co. ins Zentrum stellt.

189 | ]]>
190 |
191 | 192 | Java Runtime Zing verdoppelt die maximale Speichergröße auf 2 TB 193 | http://heise.de/-3086807 194 | 2016-01-29T09:58:31+01:00 195 | 2016-01-29T09:58:00+01:00 196 | 197 | Die Java Virtual Machine Zing ist speziell auf speicherhungrige Anwendungen ausgelegt. Mit Version 16.1 darf der dynamische Speicher auf 2 Terabyte steigen. 198 | 200 | 201 | Java Runtime Zing verdoppelt die maximale Speichergröße auf 2 TB 202 | 203 | 204 |

Die Java Virtual Machine Zing ist speziell auf speicherhungrige Anwendungen ausgelegt. Mit Version 16.1 darf der dynamische Speicher auf 2 Terabyte steigen.

205 | ]]>
206 |
207 | 208 | C# 7 – Stand der Dinge und Ausblick 209 | http://heise.de/-3086504 210 | 2016-01-29T10:33:20+01:00 211 | 2016-01-29T09:00:00+01:00 212 | 213 | Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt. 214 | 216 | 217 | C# 7 - Stand der Dinge und Ausblick 218 | 219 | 220 |

Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt.

221 | ]]>
222 |
223 | 224 | Apache Software Foundation bekommt ein neues Logo 225 | http://heise.de/-3086458 226 | 2016-01-28T17:07:04+01:00 227 | 2016-01-28T17:07:00+01:00 228 | 229 | Das neue Logos soll zugleich die bisherige Vergangenheit und den zukunftsorientierten energischen Wachstum der Open-Source-Organisation reflektieren. 230 | 232 | 233 | Apache bekommt ein neues Logo 234 | 235 | 236 |

Das neue Logos soll zugleich die bisherige Vergangenheit und den zukunftsorientierten energischen Wachstum der Open-Source-Organisation reflektieren.

237 | ]]>
238 |
239 |
240 | 241 | -------------------------------------------------------------------------------- /test/input/heraldsun.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RSS0.92 Example 5 | http://www.oreilly.com/example/index.html 6 | This is an example RSS0.91 feed 7 | en-gb 8 | Copyright 2002, Oreilly and Associates. 9 | editor@oreilly.com 10 | webmaster@oreilly.com 11 | 5 12 | 03 Apr 02 1500 GMT 13 | 03 Apr 02 1500 GMT 14 | 15 | The First Item 16 | http://www.oreilly.com/example/001.html 17 | This is the first item. 18 | Another Site 19 | 20 | Business/Industries/Publishing/Publishers/Nonfiction/ 21 | 22 | 23 | The Second Item 24 | http://www.oreilly.com/example/002.html 25 | This is the second item. 26 | Another Site 27 | 28 | Business/Industries/Publishing/Publishers/Nonfiction/ 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/input/incomplete-fields.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/input/instant-article.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Instant Article Test 5 | https://localhost:8000 6 | 1, 2, 1, 2… check the mic! 7 | en 8 | Fri, 13 May 2016 15:14:05 GMT 9 | 2016-05-13T15:14:05Z 10 | en 11 | 12 | My first Instant Article 13 | https://localhost:8000 14 | Lorem ipsum 15 | <b>Lorem</b> ipsum 16 | Wed, 04 May 2016 06:53:45 GMT 17 | https://localhost:8000 18 | tobi 19 | 2016-05-04T06:53:45Z 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/input/item-itunes-episodeType.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Baywatch Berlin 9 | de 10 | Mon, 18 Nov 2019 19:19:26 +0000 11 | Fri, 18 Jun 2021 09:57:01 +0000 12 | Das Beautiful Mind Klaas Heufer-Umlauf probiert in diesem Podcast nach über 10 Jahren weltfremden Jet Set Spaß Kontakt zur echten Welt aufzunehmen. 13 | Wie einst in der Weihnachtsgeschichte wird er dabei von seinen Freunden Thomas Schmitt und Jakob Lundt an die Hand genommen und langsam wieder mit den Themen des wahren Lebens in Kontakt gebracht. Tauchen Sie ein und seien Sie die Kugel im Flipperautomaten „Baywatch Berlin“! 14 | https://baywatch-berlin.podigee.io/ 15 | Podigee (https://podigee.com) 16 | episodic 17 | 18 | https://images.podigee-cdn.net/0x,sKl1jBkJzQhalasSZk-t081rvkZ1jaMq35213ibRTr2U=/https://cdn.podigee.com/uploads/u10314/70a0b394-56a9-47ad-883e-38493268d6bb.jpg 19 | Baywatch Berlin 20 | https://baywatch-berlin.podigee.io/ 21 | 22 | 23 | 24 | Klaas Heufer-Umlauf, Thomas Schmitt und Jakob Lundt 25 | no 26 | klaas,late night berlin,klaas heufer umlauf,baywatch berlin,baywatch 27 | 28 | 29 | 30 | Das Beautiful Mind Klaas Heufer-Umlauf probiert in diesem Podcast nach über 10 Jahren weltfremden Jet Set Spaß Kontakt zur echten Welt aufzunehmen. 31 | Wie einst in der Weihnachtsgeschichte wird er dabei von seinen Freunden Thomas Schmitt und Jakob Lundt an die Hand genommen und langsam wieder mit den Themen des wahren Lebens in Kontakt gebracht. Tauchen Sie ein und seien Sie die Kugel im Flipperautomaten „Baywatch Berlin“! 32 | 33 | Klaas Heufer-Umlauf, Thomas Schmitt und Jakob Lundt 34 | alexander.krawczyk@starwatch.de 35 | 36 | 37 | Trailer 38 | Trailer 39 | Baywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen. 40 | Mon, 18 Nov 2019 19:14:32 +0000 41 | https://baywatch-berlin.podigee.io/t1-trailer 42 | cc973b648d6fb41e4e5ef7e4b4ee7604 43 | 44 | 46 | 47 | 48 | 1 49 | trailer 50 | Baywatch Berlin 51 | Baywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen. 52 | no 53 | klaas,baywatch,berlin,late night berlin,joko und klaas,duell um die welt 54 | Klaas Heufer-Umlauf, Thomas Schmitt und Jakob Lundt 55 | 56 | 114 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/input/itunes-category.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Taverncast - Happy Hour in Your Head - Since 2005 5 | Bryce Erwin, Bill Ticknor, Michelle O'Neill, Mike Monan, Aric Watson, Jennifer Albrecht, Lauren Hoban and Derek Chew 6 | http://www.taverncast.com/ 7 | Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head! 8 | Beer - Talk - Fun. It's Happy Hour in Your Head! 9 | Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head! 10 | en-us 11 | Taverncast, 2015 12 | taverncast@taverncast.com 13 | Wed, 03 Aug 2005 18:00:00 GMT 14 | me mitts 15 | 16 | Taverncast 17 | taverncast@taverncast.com 18 | 19 | no 20 | 21 | http://www.taverncast.com/itunes-logo.png 22 | Taverncast - Happy Hour in Your Head - Since 2005 23 | http://www.taverncast.com/ 24 | 144 25 | 300 26 | 27 | Taverncast 28 | href=http://www.taverncast.com/itunes-logo.png"> 29 | 30 | 31 | 32 | 33 | 34 | Taverncast 62 - Temporal Anomaly 35 | http://taverncast.com/ 36 | Taverncast: www.taverncast.com 37 | 38 | http://www.taverncast.com 39 | Taverncast 40 | Taverncast 41 | Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog. 42 | Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog. 43 | Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog. 44 | 45 | http://taverncast.com/shows/taverncast-62.mp3 46 | 07 Nov 2015 12:00:00 EST 47 | Society & Culture 48 | no 49 | 1:09:50 50 | Taverncast, beer, games, hobbies, gaming, computer, geek, party, movies, comics, pop-culture, weird, comedy, humor, funny, talk, nerd, news, politics, paranormal, conspiracy, morning show, stone brewing company, time travel, back to the future, past, future, present, timeline, temporal, travel, time, dog, doodle, poodle, apple, laptop, computer, arrogant, bastard, arrogant bastard 51 | 52 | 53 | -------------------------------------------------------------------------------- /test/input/itunes-href.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Transfermarkt 5 | http://www.transfermarkt.de/rss/news 6 | 8 | Transfermarkt 9 | http://www.transfermarkt.de 10 | http://www.transfermarkt.de/images/logo.png 11 | 12 | de-de 13 | Mon, 07 Jan 2019 18:12:17 +0100 14 | http://www.transfermarkt.de/rss/news 15 | 16 | 17 | Manager bewertet Transfers | Eberl lobt BVB für Pulisic-Deal – Hudson-Odoi kostet „fast so viel wie mein Kader“ 18 | 19 | http://www.transfermarkt.de/eberl-lobt-bvb-fur-pulisic-deal-ndash-hudson-odoi-kostet-bdquo-fast-so-viel-wie-mein-kader-ldquo-/view/news/326649?rss 20 | 21 | Mon, 07 Jan 2019 17:19:19 +0100 22 | 25 | 26 | Transfermarkt 27 | 28 | http://www.transfermarkt.de/eberl-lobt-bvb-fur-pulisic-deal-ndash-hudson-odoi-kostet-bdquo-fast-so-viel-wie-mein-kader-ldquo-/view/news/326649?rss 29 | 30 | 31 | 32 | Im zweiten Anlauf | Magdeburg verleiht Talent Harant – Fünfter FCM-Spieler in Halberstadt 33 | 34 | http://www.transfermarkt.de/magdeburg-verleiht-talent-harant-ndash-funfter-fcm-spieler-in-halberstadt/view/news/326660?rss 35 | 36 | Mon, 07 Jan 2019 16:55:00 +0100 37 | 40 | 41 | Transfermarkt 42 | 43 | http://www.transfermarkt.de/magdeburg-verleiht-talent-harant-ndash-funfter-fcm-spieler-in-halberstadt/view/news/326660?rss 44 | 45 | 46 | 47 | Transfer fix | Iborra zurück nach Spanien: Villarreal plant langfristig mit Leicesters Reservisten 48 | 49 | http://www.transfermarkt.de/iborra-zuruck-nach-spanien-villarreal-plant-langfristig-mit-leicesters-reservisten/view/news/326658?rss 50 | 51 | Mon, 07 Jan 2019 16:29:39 +0100 52 | 55 | 56 | Transfermarkt 57 | 58 | http://www.transfermarkt.de/iborra-zuruck-nach-spanien-villarreal-plant-langfristig-mit-leicesters-reservisten/view/news/326658?rss 59 | 60 | 61 | 62 | Jetzt bei TM.tv | BVB-Interesse an Werner: Philipp im Gegenzug nach Leipzig? 63 | 64 | http://www.transfermarkt.de/bvb-interesse-an-werner-philipp-im-gegenzug-nach-leipzig-/view/news/326652?rss 65 | 66 | Mon, 07 Jan 2019 16:00:00 +0100 67 | 70 | 71 | Transfermarkt 72 | 73 | http://www.transfermarkt.de/bvb-interesse-an-werner-philipp-im-gegenzug-nach-leipzig-/view/news/326652?rss 74 | 75 | 76 | 77 | Rechtsverteidiger | Dritter Wintertransfer: Däne Paulsen soll Ingolstadt aus der Misere helfen 78 | 79 | http://www.transfermarkt.de/dritter-wintertransfer-dane-paulsen-soll-ingolstadt-aus-der-misere-helfen/view/news/326654?rss 80 | 81 | Mon, 07 Jan 2019 15:46:15 +0100 82 | 85 | 86 | Transfermarkt 87 | 88 | http://www.transfermarkt.de/dritter-wintertransfer-dane-paulsen-soll-ingolstadt-aus-der-misere-helfen/view/news/326654?rss 89 | 90 | 91 | 92 | Vertrag mit Option | Baffo darf sich bis Sommer in Duisburg beweisen – „Wir mussten reagieren“ 93 | 94 | http://www.transfermarkt.de/baffo-darf-sich-bis-sommer-in-duisburg-beweisen-ndash-bdquo-wir-mussten-reagieren-ldquo-/view/news/326647?rss 95 | 96 | Mon, 07 Jan 2019 15:19:03 +0100 97 | 100 | 101 | Transfermarkt 102 | 103 | http://www.transfermarkt.de/baffo-darf-sich-bis-sommer-in-duisburg-beweisen-ndash-bdquo-wir-mussten-reagieren-ldquo-/view/news/326647?rss 104 | 105 | 106 | 107 | Nach Haidara-Transfer | Bericht: Salzburg-Führung verhinderte Wolf-Wechsel nach Leipzig – Veto von Rose 108 | 109 | http://www.transfermarkt.de/bericht-salzburg-fuhrung-verhinderte-wolf-wechsel-nach-leipzig-ndash-veto-von-rose/view/news/326640?rss 110 | 111 | Mon, 07 Jan 2019 14:47:00 +0100 112 | 115 | 116 | Transfermarkt 117 | 118 | http://www.transfermarkt.de/bericht-salzburg-fuhrung-verhinderte-wolf-wechsel-nach-leipzig-ndash-veto-von-rose/view/news/326640?rss 119 | 120 | 121 | 122 | Wechsel in die Regionalliga | Regensburg verabschiedet „verdienten Jahn-Profi“ Kopp nach Bayreuth 123 | 124 | http://www.transfermarkt.de/regensburg-verabschiedet-bdquo-verdienten-jahn-profi-ldquo-kopp-nach-bayreuth/view/news/326639?rss 125 | 126 | Mon, 07 Jan 2019 14:15:21 +0100 127 | 130 | 131 | Transfermarkt 132 | 133 | http://www.transfermarkt.de/regensburg-verabschiedet-bdquo-verdienten-jahn-profi-ldquo-kopp-nach-bayreuth/view/news/326639?rss 134 | 135 | 136 | 137 | Regionalliga West | Zschiesche übernimmt Trainerposten beim Bonner SC 138 | 139 | http://www.transfermarkt.de/zschiesche-ubernimmt-trainerposten-beim-bonner-sc/view/news/326633?rss 140 | 141 | Mon, 07 Jan 2019 14:01:09 +0100 142 | 145 | 146 | Transfermarkt 147 | 148 | http://www.transfermarkt.de/zschiesche-ubernimmt-trainerposten-beim-bonner-sc/view/news/326633?rss 149 | 150 | 151 | 152 | Chelsea plant Angebot | Cagliari erwartet Rekordablöse für Barella: Teuerster Italiener nach Jorginho & Buffon? 153 | 154 | http://www.transfermarkt.de/cagliari-erwartet-rekordablose-fur-barella-teuerster-italiener-nach-jorginho-amp-buffon-/view/news/326629?rss 155 | 156 | Mon, 07 Jan 2019 13:51:00 +0100 157 | 160 | 161 | Transfermarkt 162 | 163 | http://www.transfermarkt.de/cagliari-erwartet-rekordablose-fur-barella-teuerster-italiener-nach-jorginho-amp-buffon-/view/news/326629?rss 164 | 165 | -------------------------------------------------------------------------------- /test/input/itunes-keywords-array.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | SWR2 Impuls - Wissen aktuell 8 | https://www.swr.de/swr2/programm/SWR2-Wissen-Impuls-Podcast,swr2-impuls-podcast-100.html 9 | 10 | Neues aus Wissenschaft, Medizin, Umwelt und Bildung. 11 | Neues aus Wissenschaft, Medizin, Umwelt und Bildung. 12 | de 13 | Copyright © Südwestrundfunk 14 | 15 | https://www.swr.de/swr2/programm/Podcastbild-SWR2-Impuls,1564741489275,swr2-impuls-podcast-104~_v-16x9@2dS_-6be50a9c75559ca1aaf1d0b25bae287afdcd877a.jpg 16 | SWR2 Impuls - Wissen aktuell 17 | https://www.swr.de/swr2/programm/SWR2-Wissen-Impuls-Podcast,swr2-impuls-podcast-100.html 18 | 19 | Wed, 9 Oct 2019 16:39:00 +0200 20 | Neues aus Wissenschaft, Medizin, Umwelt und Bildung. Dazu Musik, die sich vom Mainstream abhebt. Montags bis freitags um 16:05. 21 | SWR 22 | Neues aus Wissenschaft, Medizin, Umwelt und Bildung. 23 | 24 | Südwestrundfunk 25 | podcast@swr.de 26 | 27 | Südwestrundfunk 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | No 38 | No 39 | 40 | 41 | Ostafrika stellt sich auf die nächste Heuschreckenplage ein 42 | https://www.swr.de/swr2/wissen/Hungersnot-Ostafrika-stellt-sich-auf-die-naechste-Heuschreckenplage-ein,ostafrika-stellt-sich-auf-naechste-heuschreckenplage-ein-100.html 43 | Nach der jüngsten Heuschreckenplage droht Ostafrika neues Unheil. Es regnet mehr als sonst. Der feuchte Boden ist ein idealer Brutkasten für die nächste Generation Heuschrecken. Die ohnehin angespannte Ernährungslage könnte sich weiter verschlechtern. 44 | Nach der jüngsten Heuschreckenplage droht Ostafrika neues Unheil. Es regnet mehr als sonst. Der feuchte Boden ist ein idealer Brutkasten für die nächste Generation Heuschrecken. Die ohnehin angespannte Ernährungslage könnte sich weiter verschlechtern. 45 | Wissen,Heuschrecken,Heuschreckenschwärme,Heuschreckenplage,Ostafrika,Natur 46 | Tue, 10 Mar 2020 16:05:00 +0100 47 | c88fbba9-c228-4724-bf41-8c193b72491c 48 | 49 | 52 | 05:09 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/input/itunes-keywords.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Taverncast - Happy Hour in Your Head - Since 2005 5 | Bryce Erwin, Bill Ticknor, Michelle O'Neill, Mike Monan, Aric Watson, Jennifer Albrecht, Lauren Hoban and Derek Chew 6 | http://www.taverncast.com/ 7 | Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head! 8 | Beer - Talk - Fun. It's Happy Hour in Your Head! 9 | Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head! 10 | en-us 11 | Taverncast, 2015 12 | taverncast@taverncast.com 13 | Wed, 03 Aug 2005 18:00:00 GMT 14 | me mitts 15 | 16 | Taverncast 17 | taverncast@taverncast.com 18 | 19 | no 20 | 21 | http://www.taverncast.com/itunes-logo.png 22 | Taverncast - Happy Hour in Your Head - Since 2005 23 | http://www.taverncast.com/ 24 | 144 25 | 300 26 | 27 | Taverncast 28 | href=http://www.taverncast.com/itunes-logo.png"> 29 | Culture,Society 30 | 31 | Taverncast 62 - Temporal Anomaly 32 | http://taverncast.com/ 33 | Taverncast: www.taverncast.com 34 | 35 | http://www.taverncast.com 36 | Taverncast 37 | Taverncast 38 | Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog. 39 | Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog. 40 | Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog. 41 | 42 | http://taverncast.com/shows/taverncast-62.mp3 43 | 07 Nov 2015 12:00:00 EST 44 | Society & Culture 45 | no 46 | 1:09:50 47 | Taverncast, beer, games, hobbies, gaming, computer, geek, party, movies, comics, pop-culture, weird, comedy, humor, funny, talk, nerd, news, politics, paranormal, conspiracy, morning show, stone brewing company, time travel, back to the future, past, future, present, timeline, temporal, travel, time, dog, doodle, poodle, apple, laptop, computer, arrogant, bastard, arrogant bastard 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/input/missing-fields.atom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tag:github.com,2008:Repository/11167738/v3.9.0 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/input/narro.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | foobar on Narro 5 | http://on.narro.co/f 6 | foobar uses Narro to create a podcast of articles transcribed to audio. 7 | en 8 | All article content copyright of respective source authors. 9 | foobar@gmail.com 10 | josh@narro.co 11 | Fri, 08 Jul 2016 13:40:00 UTC 12 | gopod - http://github.com/jbckmn/gopod 13 | 20 14 | foobar@gmail.com 15 | foobar uses Narro to create a podcast of articles transcribed to audio. 16 | foobar uses Narro to create a podcast of articles transcribed to audio. 17 | no 18 | 19 | foobar 20 | foobar@gmail.com 21 | 22 | 23 | 24 | 25 | https://www.narro.co/article/54e703933058540300000069 26 | Listen to your reading list anywhere. Narro will take your bookmarked articles and read them back to you as a podcast.<br/> http://www.narro.co/faq<br/> <ul class="linkList"></ul> 27 | FAQ for Narro 28 | Fri, 20 Feb 2015 09:51:15 UTC 29 | foobar@gmail.com 30 | https://www.narro.co/article/54e703933058540300000069 31 | foobar@gmail.com 32 | FAQ for Narro 33 | Listen to your reading list anywhere. Narro will take your bookmarked articles and read them back to you as a podcast. ... http://www.narro.co/faq ... <ul class="linkList"></ul> 34 | no 35 | 74 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/input/pagination-links.rss: -------------------------------------------------------------------------------- 1 | 2 | 3 | New Show - With Episode Block 4 | 5 | 6 | 46 | -------------------------------------------------------------------------------- /test/input/reddit-atom.rss: -------------------------------------------------------------------------------- 1 | reddit: the front page of the internethttps://www.reddit.com/https://www.redditstatic.com/reddit.com.header.pngreddit: the front page of the internethttps://www.reddit.com/The Difficulties in Spacespacehttps://www.reddit.com/r/space/comments/42babr/the_difficulties_in_space/https://www.reddit.com/r/space/comments/42babr/the_difficulties_in_space/Sat, 23 Jan 2016 15:40:37 +0000
The Difficulties in Spacesubmitted bybenjaab123tospace
[link][959 comments]
The Difficulties in Space
Driving for Uber in the Virginia last nightpicshttps://www.reddit.com/r/pics/comments/42bic6/driving_for_uber_in_the_virginia_last_night/https://www.reddit.com/r/pics/comments/42bic6/driving_for_uber_in_the_virginia_last_night/Sat, 23 Jan 2016 16:36:05 +0000
Driving for Uber in the Virginia last nightsubmitted byeskimiotopics
[link][533 comments]
Driving for Uber in the Virginia last night
Today I found out how to remotely control my high school student's computers and how to send them messages when they're not doing the right thingfunnyhttps://www.reddit.com/r/funny/comments/42b7ov/today_i_found_out_how_to_remotely_control_my_high/https://www.reddit.com/r/funny/comments/42b7ov/today_i_found_out_how_to_remotely_control_my_high/Sat, 23 Jan 2016 15:22:27 +0000
Today I found out how to remotely control my high school student's computers and how to send them messages when they're not doing the right thingsubmitted byyungscotttofunny
[link][1609 comments]
Today I found out how to remotely control my high school student's computers and how to send them messages when they're not doing the right thing
TIL that xbox360 can be used as an projector. Atleast thats what Ikea make me believe.gaminghttps://www.reddit.com/r/gaming/comments/42b3j9/til_that_xbox360_can_be_used_as_an_projector/https://www.reddit.com/r/gaming/comments/42b3j9/til_that_xbox360_can_be_used_as_an_projector/Sat, 23 Jan 2016 14:51:51 +0000
TIL that xbox360 can be used as an projector. Atleast thats what Ikea make me believe.submitted bykleutschertogaming
[link][296 comments]
TIL that xbox360 can be used as an projector. Atleast thats what Ikea make me believe.
Bill Cosby wins defamation casenewshttps://www.reddit.com/r/news/comments/42b39d/bill_cosby_wins_defamation_case/https://www.reddit.com/r/news/comments/42b39d/bill_cosby_wins_defamation_case/Sat, 23 Jan 2016 14:49:30 +0000submitted byObParkstonews
[link][1804 comments]
Super Crab!gifshttps://www.reddit.com/r/gifs/comments/42b12w/super_crab/https://www.reddit.com/r/gifs/comments/42b12w/super_crab/Sat, 23 Jan 2016 14:32:28 +0000
Super Crab!submitted bytarynsouthernietogifs
[link][650 comments]
Super Crab!
Japan accepts 27 refugees last year, rejects 99%worldnewshttps://www.reddit.com/r/worldnews/comments/42axuv/japan_accepts_27_refugees_last_year_rejects_99/https://www.reddit.com/r/worldnews/comments/42axuv/japan_accepts_27_refugees_last_year_rejects_99/Sat, 23 Jan 2016 14:05:46 +0000submitted byconanthekingtoworldnews
[link][5282 comments]
Tap shower time!awwhttps://www.reddit.com/r/aww/comments/42aukl/tap_shower_time/https://www.reddit.com/r/aww/comments/42aukl/tap_shower_time/Sat, 23 Jan 2016 13:33:58 +0000
Tap shower time!submitted bytheone1221toaww
[link][578 comments]
Tap shower time!
TIL Sleeping Beauty herself only has 18 lines of dialogue in the entire movie.todayilearnedhttps://www.reddit.com/r/todayilearned/comments/42awq6/til_sleeping_beauty_herself_only_has_18_lines_of/https://www.reddit.com/r/todayilearned/comments/42awq6/til_sleeping_beauty_herself_only_has_18_lines_of/Sat, 23 Jan 2016 13:55:20 +0000
TIL Sleeping Beauty herself only has 18 lines of dialogue in the entire movie.submitted bymoonspritetotodayilearned
[link][693 comments]
TIL Sleeping Beauty herself only has 18 lines of dialogue in the entire movie.
The most perfect vanilla bean cheesecake I've ever madefoodhttps://www.reddit.com/r/food/comments/42b8ok/the_most_perfect_vanilla_bean_cheesecake_ive_ever/https://www.reddit.com/r/food/comments/42b8ok/the_most_perfect_vanilla_bean_cheesecake_ive_ever/Sat, 23 Jan 2016 15:29:25 +0000
The most perfect vanilla bean cheesecake I've ever madesubmitted byIwantsprinklestofood
[link][165 comments]
The most perfect vanilla bean cheesecake I've ever made
Tobey Maguire and Leonardo DiCaprio bowling, 1989OldSchoolCoolhttps://www.reddit.com/r/OldSchoolCool/comments/42avr3/tobey_maguire_and_leonardo_dicaprio_bowling_1989/https://www.reddit.com/r/OldSchoolCool/comments/42avr3/tobey_maguire_and_leonardo_dicaprio_bowling_1989/Sat, 23 Jan 2016 13:45:28 +0000
Tobey Maguire and Leonardo DiCaprio bowling, 1989submitted byKjell_AronsentoOldSchoolCool
[link][347 comments]
Tobey Maguire and Leonardo DiCaprio bowling, 1989
This telephone box is now an ATMmildlyinterestinghttps://www.reddit.com/r/mildlyinteresting/comments/42aq29/this_telephone_box_is_now_an_atm/https://www.reddit.com/r/mildlyinteresting/comments/42aq29/this_telephone_box_is_now_an_atm/Sat, 23 Jan 2016 12:48:32 +0000
This telephone box is now an ATMsubmitted bysdsragetomildlyinteresting
[link][410 comments]
This telephone box is now an ATM
Reddit cares more about Leonardo's Oscar than Leonardo does.Showerthoughtshttps://www.reddit.com/r/Showerthoughts/comments/42axmv/reddit_cares_more_about_leonardos_oscar_than/https://www.reddit.com/r/Showerthoughts/comments/42axmv/reddit_cares_more_about_leonardos_oscar_than/Sat, 23 Jan 2016 14:03:51 +0000submitted byProfessorRedstoShowerthoughts
[link][341 comments]
Pearl Jam donates $300,000 to Flint water crisisUpliftingNewshttps://www.reddit.com/r/UpliftingNews/comments/42bgbh/pearl_jam_donates_300000_to_flint_water_crisis/https://www.reddit.com/r/UpliftingNews/comments/42bgbh/pearl_jam_donates_300000_to_flint_water_crisis/Sat, 23 Jan 2016 16:22:41 +0000
Pearl Jam donates $300,000 to Flint water crisissubmitted bySariel007toUpliftingNews
[link][194 comments]
Pearl Jam donates $300,000 to Flint water crisis
Which persistent misconception/myth annoys you the most?AskReddithttps://www.reddit.com/r/AskReddit/comments/42asa3/which_persistent_misconceptionmyth_annoys_you_the/https://www.reddit.com/r/AskReddit/comments/42asa3/which_persistent_misconceptionmyth_annoys_you_the/Sat, 23 Jan 2016 13:11:40 +0000submitted byadeebchowdhurytoAskReddit
[link][10672 comments]
Robot solves Rubik's Cube in 1.1 secondsvideoshttps://www.reddit.com/r/videos/comments/42b5vv/robot_solves_rubiks_cube_in_11_seconds/https://www.reddit.com/r/videos/comments/42b5vv/robot_solves_rubiks_cube_in_11_seconds/Sat, 23 Jan 2016 15:09:31 +0000
Robot solves Rubik's Cube in 1.1 secondssubmitted bycoder13tovideos
[link][287 comments]
Robot solves Rubik's Cube in 1.1 seconds
I am the guy who dragged a model into the ocean and tied her up with sharks for conservation. Photographer VonWong, AMAIAmAhttps://www.reddit.com/r/IAmA/comments/42bx8o/i_am_the_guy_who_dragged_a_model_into_the_ocean/https://www.reddit.com/r/IAmA/comments/42bx8o/i_am_the_guy_who_dragged_a_model_into_the_ocean/Sat, 23 Jan 2016 18:08:59 +0000&lt;!-- SC_OFF --&gt;&lt;div class=&quot;md&quot;&gt;&lt;p&gt;Hey Reddit! Benjamin Von Wong here, glad to finally be back here for another ama. Last week &lt;a href=&quot;http://www.vonwong.com/blog/sharkshepherd/&quot;&gt;I dragged a model into the shark ridden sea&lt;/a&gt; in hopes of drawing attention towards the need for &lt;a href=&quot;https://www.change.org/p/support-malaysian-shark-sanctuaries&quot;&gt;a shark sanctuary in Malaysia&lt;/a&gt;. The response from reddit has been &lt;a href=&quot;https://www.reddit.com/r/pics/comments/41lfna/a_friend_of_mine_just_took_this_photo_during_a/&quot;&gt;pretty huge&lt;/a&gt;, so I wanted to answer any questions you might have about me and or life and the universe in general. &lt;/p&gt; &lt;p&gt;Proof: &lt;a href=&quot;https://twitter.com/thevonwong/status/690957921530855424&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://www.facebook.com/thevonwong/posts/10153516513968515&quot;&gt;Facebook&lt;/a&gt;&lt;br/&gt; My site: &lt;a href=&quot;http://vonwong.com&quot;&gt;http://vonwong.com&lt;/a&gt;&lt;/p&gt; &lt;/div&gt;&lt;!-- SC_ON --&gt; submitted byvonwongtoIAmA
[link][339 comments]
Te Hoho Rock from New Zealand's Cathedral Cove [2048×1365] Photographed by Greg NessEarthPornhttps://www.reddit.com/r/EarthPorn/comments/42at7b/te_hoho_rock_from_new_zealands_cathedral_cove/https://www.reddit.com/r/EarthPorn/comments/42at7b/te_hoho_rock_from_new_zealands_cathedral_cove/Sat, 23 Jan 2016 13:20:30 +0000
Te Hoho Rock from New Zealand's Cathedral Cove [2048×1365] Photographed by Greg Nesssubmitted byTopdeBottontoEarthPorn
[link][93 comments]
Te Hoho Rock from New Zealand's Cathedral Cove [2048×1365] Photographed by Greg Ness
A diet rich in fiber may not only protect against diabetes and heart disease, it may reduce the risk of developing lung disease, according to new research published online, ahead of print in the Annals of the American Thoracic Society.sciencehttps://www.reddit.com/r/science/comments/42az5m/a_diet_rich_in_fiber_may_not_only_protect_against/https://www.reddit.com/r/science/comments/42az5m/a_diet_rich_in_fiber_may_not_only_protect_against/Sat, 23 Jan 2016 14:16:52 +0000submitted bydustofoblivion123toscience
[link][170 comments]
Portrait Girl, Stanley Barros, digital, 2015Arthttps://www.reddit.com/r/Art/comments/42as8q/portrait_girl_stanley_barros_digital_2015/https://www.reddit.com/r/Art/comments/42as8q/portrait_girl_stanley_barros_digital_2015/Sat, 23 Jan 2016 13:11:18 +0000
Portrait Girl, Stanley Barros, digital, 2015submitted byselltheskytoArt
[link][73 comments]
Portrait Girl, Stanley Barros, digital, 2015
The World's Most Beautiful Bookstoresbookshttps://www.reddit.com/r/books/comments/42b9wm/the_worlds_most_beautiful_bookstores/https://www.reddit.com/r/books/comments/42b9wm/the_worlds_most_beautiful_bookstores/Sat, 23 Jan 2016 15:37:45 +0000
The World's Most Beautiful Bookstoressubmitted bypacodecabratobooks
[link][83 comments]
The World's Most Beautiful Bookstores
TIFU by sending my friend a fucked up meme.tifuhttps://www.reddit.com/r/tifu/comments/42bvkl/tifu_by_sending_my_friend_a_fucked_up_meme/https://www.reddit.com/r/tifu/comments/42bvkl/tifu_by_sending_my_friend_a_fucked_up_meme/Sat, 23 Jan 2016 17:58:36 +0000&lt;!-- SC_OFF --&gt;&lt;div class=&quot;md&quot;&gt;&lt;p&gt;I was laying in bed this morning browsing &lt;a href=&quot;/r/imgoingtohellforthis&quot;&gt;/r/imgoingtohellforthis&lt;/a&gt; when I came across &lt;a href=&quot;https://i.imgur.com/ZKJPgtd.jpg&quot;&gt;this meme&lt;/a&gt; that I thought was fucking hilarious. My friends have the same fucked up sense of humor that I do so I sent it to a couple of them for a good laugh. &lt;/p&gt; &lt;p&gt;I then go on Facebook and see that one of my friends posted a status that his grandma passed away last night. The same friend I just sent a meme about his grandma&amp;#39;s vaginal juices to. &lt;/p&gt; &lt;p&gt;Fuck. &lt;/p&gt; &lt;p&gt;Tl;Dr: was browsing &lt;a href=&quot;/r/imgoingtohellforthis&quot;&gt;/r/imgoingtohellforthis&lt;/a&gt;, now I am actually going to hell for it.&lt;/p&gt; &lt;/div&gt;&lt;!-- SC_ON --&gt; submitted byCaptainFlacidtotifu
[link][89 comments]
What do you call 5 black people having sex?Jokeshttps://www.reddit.com/r/Jokes/comments/42aj2i/what_do_you_call_5_black_people_having_sex/https://www.reddit.com/r/Jokes/comments/42aj2i/what_do_you_call_5_black_people_having_sex/Sat, 23 Jan 2016 11:31:15 +0000&lt;!-- SC_OFF --&gt;&lt;div class=&quot;md&quot;&gt;&lt;p&gt;A threesome&lt;/p&gt; &lt;/div&gt;&lt;!-- SC_ON --&gt; submitted byillestprodigytoJokes
[link][607 comments]
Mutual Peace Offerings at UFC Weigh-Inssportshttps://www.reddit.com/r/sports/comments/42ayjn/mutual_peace_offerings_at_ufc_weighins/https://www.reddit.com/r/sports/comments/42ayjn/mutual_peace_offerings_at_ufc_weighins/Sat, 23 Jan 2016 14:11:56 +0000
Mutual Peace Offerings at UFC Weigh-Inssubmitted byvaticanxxtosports
[link][238 comments]
Mutual Peace Offerings at UFC Weigh-Ins
ELI5: Why does a computer sometimes have a hard time starting up, and what exactly does a Start Up Repair do to fix it?explainlikeimfivehttps://www.reddit.com/r/explainlikeimfive/comments/42bh58/eli5_why_does_a_computer_sometimes_have_a_hard/https://www.reddit.com/r/explainlikeimfive/comments/42bh58/eli5_why_does_a_computer_sometimes_have_a_hard/Sat, 23 Jan 2016 16:28:07 +0000submitted byVY_Cannabis_Majoristoexplainlikeimfive
[link][132 comments]
-------------------------------------------------------------------------------- /test/input/unrecognized.rss: -------------------------------------------------------------------------------- 1 | Document Moved 2 |

Object Moved

This document may be found here -------------------------------------------------------------------------------- /test/input/uolNoticias.rss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbren/rss-parser/e9b21691d25cc125c7a4d7be9835e318aee5bc70/test/input/uolNoticias.rss -------------------------------------------------------------------------------- /test/output/encoding.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbren/rss-parser/e9b21691d25cc125c7a4d7be9835e318aee5bc70/test/output/encoding.json -------------------------------------------------------------------------------- /test/output/gulp-atom.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "v3.9.0", 6 | "link": "/gulpjs/gulp/releases/tag/v3.9.0", 7 | "pubDate": "2015-06-01T21:49:41.000Z", 8 | "author": "contra", 9 | "content": "

3.9.0

", 10 | "contentSnippet": "3.9.0", 11 | "id": "tag:github.com,2008:Repository/11167738/v3.9.0", 12 | "isoDate": "2015-06-01T21:49:41.000Z" 13 | }, 14 | { 15 | "title": "v3.8.11", 16 | "link": "/gulpjs/gulp/releases/tag/v3.8.11", 17 | "pubDate": "2015-02-09T20:37:28.000Z", 18 | "author": "contra", 19 | "content": "

3.8.11

", 20 | "contentSnippet": "3.8.11", 21 | "id": "tag:github.com,2008:Repository/11167738/v3.8.11", 22 | "isoDate": "2015-02-09T20:37:28.000Z" 23 | }, 24 | { 25 | "title": "v3.8.10", 26 | "link": "/gulpjs/gulp/releases/tag/v3.8.10", 27 | "pubDate": "2014-11-04T00:11:30.000Z", 28 | "author": "contra", 29 | "content": "

3.8.10

", 30 | "contentSnippet": "3.8.10", 31 | "id": "tag:github.com,2008:Repository/11167738/v3.8.10", 32 | "isoDate": "2014-11-04T00:11:30.000Z" 33 | }, 34 | { 35 | "title": "v3.8.9", 36 | "link": "/gulpjs/gulp/releases/tag/v3.8.9", 37 | "pubDate": "2014-10-22T06:55:20.000Z", 38 | "author": "contra", 39 | "content": "

3.8.9

", 40 | "contentSnippet": "3.8.9", 41 | "id": "tag:github.com,2008:Repository/11167738/v3.8.9", 42 | "isoDate": "2014-10-22T06:55:20.000Z" 43 | }, 44 | { 45 | "title": "v3.8.8", 46 | "link": "/gulpjs/gulp/releases/tag/v3.8.8", 47 | "pubDate": "2014-09-07T20:20:11.000Z", 48 | "author": "contra", 49 | "content": "

3.8.8

", 50 | "contentSnippet": "3.8.8", 51 | "id": "tag:github.com,2008:Repository/11167738/v3.8.8", 52 | "isoDate": "2014-09-07T20:20:11.000Z" 53 | }, 54 | { 55 | "title": "v3.8.7", 56 | "link": "/gulpjs/gulp/releases/tag/v3.8.7", 57 | "pubDate": "2014-08-02T04:58:19.000Z", 58 | "author": "contra", 59 | "content": "

3.8.7

", 60 | "contentSnippet": "3.8.7", 61 | "id": "tag:github.com,2008:Repository/11167738/v3.8.7", 62 | "isoDate": "2014-08-02T04:58:19.000Z" 63 | }, 64 | { 65 | "title": "v3.8.6", 66 | "link": "/gulpjs/gulp/releases/tag/v3.8.6", 67 | "pubDate": "2014-07-09T22:06:50.000Z", 68 | "author": "contra", 69 | "content": "

3.8.6

", 70 | "contentSnippet": "3.8.6", 71 | "id": "tag:github.com,2008:Repository/11167738/v3.8.6", 72 | "isoDate": "2014-07-09T22:06:50.000Z" 73 | }, 74 | { 75 | "title": "v3.8.5", 76 | "link": "/gulpjs/gulp/releases/tag/v3.8.5", 77 | "pubDate": "2014-06-27T06:53:54.000Z", 78 | "author": "contra", 79 | "content": "

3.8.5

", 80 | "contentSnippet": "3.8.5", 81 | "id": "tag:github.com,2008:Repository/11167738/v3.8.5", 82 | "isoDate": "2014-06-27T06:53:54.000Z" 83 | }, 84 | { 85 | "title": "v3.8.4", 86 | "link": "/gulpjs/gulp/releases/tag/v3.8.4", 87 | "pubDate": "2014-06-27T06:38:43.000Z", 88 | "author": "contra", 89 | "content": "

3.8.4

", 90 | "contentSnippet": "3.8.4", 91 | "id": "tag:github.com,2008:Repository/11167738/v3.8.4", 92 | "isoDate": "2014-06-27T06:38:43.000Z" 93 | }, 94 | { 95 | "title": "v3.8.3", 96 | "link": "/gulpjs/gulp/releases/tag/v3.8.3", 97 | "pubDate": "2014-06-26T21:17:51.000Z", 98 | "author": "contra", 99 | "content": "

3.8.3

", 100 | "contentSnippet": "3.8.3", 101 | "id": "tag:github.com,2008:Repository/11167738/v3.8.3", 102 | "isoDate": "2014-06-26T21:17:51.000Z" 103 | } 104 | ], 105 | "link": "https://github.com/gulpjs/gulp/releases", 106 | "feedUrl": "https://github.com/gulpjs/gulp/releases.atom", 107 | "title": "Release notes from gulp", 108 | "lastBuildDate": "2015-06-01T23:49:41+02:00" 109 | } 110 | } -------------------------------------------------------------------------------- /test/output/heise.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "Java-Anwendungsserver: Red Hat gibt WildFly 10 frei", 6 | "link": "http://www.heise.de/developer/meldung/Java-Anwendungsserver-Red-Hat-gibt-WildFly-10-frei-3088438.html?wt_mc=rss.developer.beitrag.atom", 7 | "pubDate": "2016-02-01T16:22:00.000Z", 8 | "content": "\n \n \n \"WildFly\n \n \n

Die nun verfügbare Version 10 des Enterprise-Java-Servers stellt die Basis für Red Hats kommerzielle JBoss Enterprise Application Platform 7 ist zugleich das dritte größere Release seit dem Namenswechsel des Open-Source-Projekts.

\n ", 9 | "contentSnippet": "Die nun verfügbare Version 10 des Enterprise-Java-Servers stellt die Basis für Red Hats kommerzielle JBoss Enterprise Application Platform 7 ist zugleich das dritte größere Release seit dem Namenswechsel des Open-Source-Projekts.", 10 | "summary": "Die nun verfügbare Version 10 des Enterprise-Java-Servers stellt die Basis für Red Hats kommerzielle JBoss Enterprise Application Platform 7 ist zugleich das dritte größere Release seit dem Namenswechsel des Open-Source-Projekts.", 11 | "id": "http://heise.de/-3088438", 12 | "isoDate": "2016-02-01T16:22:00.000Z" 13 | }, 14 | { 15 | "title": "Scrum Day 2016: Bewerbungen für Vorträge und Workshops", 16 | "link": "http://www.heise.de/developer/meldung/Scrum-Day-2016-Bewerbungen-fuer-Vortraege-und-Workshops-3088627.html?wt_mc=rss.developer.beitrag.atom", 17 | "pubDate": "2016-02-01T13:29:00.000Z", 18 | "content": "\n \n \n \"Vortragseinreichungen\n \n \n

Über das Programm des zehnten Scrum Day stimmen die Teilnehmer selbst ab. Doch zuvor sind Scrum-Experten dran, sich bis Ende Februar mit einem Vortrag oder Workshop zu bewerben.

\n ", 19 | "contentSnippet": "Über das Programm des zehnten Scrum Day stimmen die Teilnehmer selbst ab. Doch zuvor sind Scrum-Experten dran, sich bis Ende Februar mit einem Vortrag oder Workshop zu bewerben.", 20 | "summary": "Über das Programm des zehnten Scrum Day stimmen die Teilnehmer selbst ab. Doch zuvor sind Scrum-Experten dran, sich bis Ende Februar mit einem Vortrag oder Workshop zu bewerben.", 21 | "id": "http://heise.de/-3088627", 22 | "isoDate": "2016-02-01T13:29:00.000Z" 23 | }, 24 | { 25 | "title": "Microsoft veröffentlicht Cordova-Erweiterung für Visual Studio Code", 26 | "link": "http://www.heise.de/developer/meldung/Microsoft-veroeffentlicht-Cordova-Erweiterung-fuer-Visual-Studio-Code-3088372.html?wt_mc=rss.developer.beitrag.atom", 27 | "pubDate": "2016-02-01T11:12:00.000Z", 28 | "content": "\n \n \n \"Microsoft\n \n \n

Mit dem für unterschiedliche Plattformen verfügbaren Code-Editor lassen sich nun auch hybride Apps für iOS, Android, Blackberry und Windows Phone auf Basis von HTML, CSS und JavaScript entwickeln.

\n ", 29 | "contentSnippet": "Mit dem für unterschiedliche Plattformen verfügbaren Code-Editor lassen sich nun auch hybride Apps für iOS, Android, Blackberry und Windows Phone auf Basis von HTML, CSS und JavaScript entwickeln.", 30 | "summary": "Mit dem für unterschiedliche Plattformen verfügbaren Code-Editor lassen sich nun auch hybride Apps für iOS, Android, Blackberry und Windows Phone auf Basis von HTML, CSS und JavaScript entwickeln.", 31 | "id": "http://heise.de/-3088372", 32 | "isoDate": "2016-02-01T11:12:00.000Z" 33 | }, 34 | { 35 | "title": "Ungewisse Zukunft des MySQLDumper-Projekts", 36 | "link": "http://www.heise.de/developer/meldung/Ungewisse-Zukunft-des-MySQLDumper-Projekts-3088410.html?wt_mc=rss.developer.beitrag.atom", 37 | "pubDate": "2016-02-01T09:34:00.000Z", 38 | "content": "\n \n \n \"Ungewisse\n \n \n

Daniel Schlichtholz hat sich von der Entwicklung des PHP- beziehungsweise Perl-Skripts verabschiedet, mit dem sich MySQL-Daten sichern und gegebenenfalls wiederherstellen lassen. Fällige Anpassungen an das neue PHP 7 würden ihn zu viel Zeit kosten.

\n ", 39 | "contentSnippet": "Daniel Schlichtholz hat sich von der Entwicklung des PHP- beziehungsweise Perl-Skripts verabschiedet, mit dem sich MySQL-Daten sichern und gegebenenfalls wiederherstellen lassen. Fällige Anpassungen an das neue PHP 7 würden ihn zu viel Zeit kosten.", 40 | "summary": "Daniel Schlichtholz hat sich von der Entwicklung des PHP- beziehungsweise Perl-Skripts verabschiedet, mit dem sich MySQL-Daten sichern und gegebenenfalls wiederherstellen lassen. Fällige Anpassungen an das neue PHP 7 würden ihn zu viel Zeit kosten.", 41 | "id": "http://heise.de/-3088410", 42 | "isoDate": "2016-02-01T09:34:00.000Z" 43 | }, 44 | { 45 | "title": "Änderungen bei der Authentifizierung in Microsofts v2.0 App Model", 46 | "link": "http://www.heise.de/developer/meldung/Aenderungen-bei-der-Authentifizierung-in-Microsofts-v2-0-App-Model-3088319.html?wt_mc=rss.developer.beitrag.atom", 47 | "pubDate": "2016-02-01T09:19:00.000Z", 48 | "content": "\n \n \n \"Änderungen\n \n \n

Microsoft ändert das v2.0 Auth Protocol zur Anmeldung an die Cloud-Dienste Microsoft Account und Azure Active Directory. Entwickler müssen aufgrund der Neuerungen vermutlich vorhandenen Code anpassen.

\n ", 49 | "contentSnippet": "Microsoft ändert das v2.0 Auth Protocol zur Anmeldung an die Cloud-Dienste Microsoft Account und Azure Active Directory. Entwickler müssen aufgrund der Neuerungen vermutlich vorhandenen Code anpassen.", 50 | "summary": "Microsoft ändert das v2.0 Auth Protocol zur Anmeldung an die Cloud-Dienste Microsoft Account und Azure Active Directory. Entwickler müssen aufgrund der Neuerungen vermutlich vorhandenen Code anpassen.", 51 | "id": "http://heise.de/-3088319", 52 | "isoDate": "2016-02-01T09:19:00.000Z" 53 | }, 54 | { 55 | "title": "Der Pragmatische Architekt: Ein gutes Szenario", 56 | "link": "http://www.heise.de/developer/artikel/Ein-gutes-Szenario-3088146.html?wt_mc=rss.developer.beitrag.atom", 57 | "pubDate": "2016-02-01T07:24:00.000Z", 58 | "content": "\n

Wie entwirft ein Softwarearchitekt am besten ein System? Dass es dafür kein einfaches Rezept gibt, ist der Komplexität und Heterogenität von Softwareprojekten geschuldet. Die Sache ist aber nicht hoffnungslos. Szenarien bieten für diesen Zweck ein sehr gutes Instrumentarium.

\n ", 59 | "contentSnippet": "Wie entwirft ein Softwarearchitekt am besten ein System? Dass es dafür kein einfaches Rezept gibt, ist der Komplexität und Heterogenität von Softwareprojekten geschuldet. Die Sache ist aber nicht hoffnungslos. Szenarien bieten für diesen Zweck ein sehr gutes Instrumentarium.", 60 | "summary": "Wie entwirft ein Softwarearchitekt am besten ein System? Dass es dafür kein einfaches Rezept gibt, ist der Komplexität und Heterogenität von Softwareprojekten geschuldet. Die Sache ist aber nicht hoffnungslos. Szenarien bieten für diesen Zweck ein sehr gutes Instrumentarium.", 61 | "id": "http://heise.de/-3088146", 62 | "isoDate": "2016-02-01T07:24:00.000Z" 63 | }, 64 | { 65 | "title": "Developer Snapshots: Programmierer-News in ein, zwei Sätzen", 66 | "link": "http://www.heise.de/developer/meldung/Developer-Snapshots-Programmierer-News-in-ein-zwei-Saetzen-3087585.html?wt_mc=rss.developer.beitrag.atom", 67 | "pubDate": "2016-01-29T14:37:00.000Z", 68 | "content": "\n \n \n \"Developer\n \n \n

heise Developer fasst jede Woche bisher vernachlässigte, aber doch wichtige Nachrichten zu Tools, Spezifikationen oder anderem zusammen – dieses Mal u.a. mit einem OCaml-Cross-Compiler für iOS, LLVM 3.8 und Cloud9-Integration in Googles Cloud-Plattform.

\n ", 69 | "contentSnippet": "heise Developer fasst jede Woche bisher vernachlässigte, aber doch wichtige Nachrichten zu Tools, Spezifikationen oder anderem zusammen – dieses Mal u.a. mit einem OCaml-Cross-Compiler für iOS, LLVM 3.8 und Cloud9-Integration in Googles Cloud-Plattform.", 70 | "summary": "heise Developer fasst jede Woche bisher vernachlässigte, aber doch wichtige Nachrichten zu Tools, Spezifikationen oder anderem zusammen – dieses Mal u.a. mit einem OCaml-Cross-Compiler für iOS, LLVM 3.8 und Cloud9-Integration in Googles Cloud-Plattform.", 71 | "id": "http://heise.de/-3087585", 72 | "isoDate": "2016-01-29T14:37:00.000Z" 73 | }, 74 | { 75 | "title": "Der Dotnet-Doktor: Auslesen und Sortieren von GPX-Dateien ", 76 | "link": "http://www.heise.de/developer/artikel/Auslesen-und-Sortieren-von-GPX-Dateien-3087150.html?wt_mc=rss.developer.beitrag.atom", 77 | "pubDate": "2016-01-29T12:59:00.000Z", 78 | "content": "\n

Die Windows PowerShell biete eine schnelle Lösung für die Aufgabe, eine größere Menge von XML-Dateien zu sortieren.

\n ", 79 | "contentSnippet": "Die Windows PowerShell biete eine schnelle Lösung für die Aufgabe, eine größere Menge von XML-Dateien zu sortieren.", 80 | "summary": "Die Windows PowerShell biete eine schnelle Lösung für die Aufgabe, eine größere Menge von XML-Dateien zu sortieren.", 81 | "id": "http://heise.de/-3087150", 82 | "isoDate": "2016-01-29T12:59:00.000Z" 83 | }, 84 | { 85 | "title": "SourceForge und Slashdot wechseln erneut den Besitzer", 86 | "link": "http://www.heise.de/developer/meldung/SourceForge-und-Slashdot-wechseln-erneut-den-Besitzer-3087034.html?wt_mc=rss.developer.beitrag.atom", 87 | "pubDate": "2016-01-29T12:02:00.000Z", 88 | "content": "\n \n \n \"SourceForge\n \n \n

Der neue Besitzer, ein US-amerikanisches Webmedia-Unternehmen, will offenbar den ramponierten Ruf der Hosting-Plattform für Open-Source-Projekte aufpolieren.

\n ", 89 | "contentSnippet": "Der neue Besitzer, ein US-amerikanisches Webmedia-Unternehmen, will offenbar den ramponierten Ruf der Hosting-Plattform für Open-Source-Projekte aufpolieren.", 90 | "summary": "Der neue Besitzer, ein US-amerikanisches Webmedia-Unternehmen, will offenbar den ramponierten Ruf der Hosting-Plattform für Open-Source-Projekte aufpolieren.", 91 | "id": "http://heise.de/-3087034", 92 | "isoDate": "2016-01-29T12:02:00.000Z" 93 | }, 94 | { 95 | "title": "Tales from the Web side: Patterns und Best Practices in JavaScript: Typüberprüfungen continued", 96 | "link": "http://www.heise.de/developer/artikel/Patterns-und-Best-Practices-in-JavaScript-Typueberpruefungen-continued-3086830.html?wt_mc=rss.developer.beitrag.atom", 97 | "pubDate": "2016-01-29T10:23:00.000Z", 98 | "content": "\n

Der instanceof-Operator in JavaScript kann in einigen Fällen problematisch sein kann, nämlich immer dann, wenn man mit mehreren Versionen der gleichen "Klasse" arbeitet. Nun werden einige Lösungsansätze skizziert.

\n ", 99 | "contentSnippet": "Der instanceof-Operator in JavaScript kann in einigen Fällen problematisch sein kann, nämlich immer dann, wenn man mit mehreren Versionen der gleichen \"Klasse\" arbeitet. Nun werden einige Lösungsansätze skizziert.", 100 | "summary": "Der instanceof-Operator in JavaScript kann in einigen Fällen problematisch sein kann, nämlich immer dann, wenn man mit mehreren Versionen der gleichen "Klasse" arbeitet. Nun werden einige Lösungsansätze skizziert.", 101 | "id": "http://heise.de/-3086830", 102 | "isoDate": "2016-01-29T10:23:00.000Z" 103 | }, 104 | { 105 | "title": "Facebook schließt Parse-Plattform", 106 | "link": "http://www.heise.de/developer/meldung/Facebook-schliesst-Parse-Plattform-3086857.html?wt_mc=rss.developer.beitrag.atom", 107 | "pubDate": "2016-01-29T09:12:00.000Z", 108 | "content": "\n \n \n \"Facebook\n \n \n

Ein Jahr bleibt Entwicklern, die den Mobile Backend as a Service nutzen, auf ein eigenes MongoDB-basiertes Angebot zu migrieren oder den nun als Open Source verfügbaren Parse-Server zu verwenden.

\n ", 109 | "contentSnippet": "Ein Jahr bleibt Entwicklern, die den Mobile Backend as a Service nutzen, auf ein eigenes MongoDB-basiertes Angebot zu migrieren oder den nun als Open Source verfügbaren Parse-Server zu verwenden.", 110 | "summary": "Ein Jahr bleibt Entwicklern, die den Mobile Backend as a Service nutzen, auf ein eigenes MongoDB-basiertes Angebot zu migrieren oder den nun als Open Source verfügbaren Parse-Server zu verwenden.", 111 | "id": "http://heise.de/-3086857", 112 | "isoDate": "2016-01-29T09:12:00.000Z" 113 | }, 114 | { 115 | "title": "Continuous Lifecycle London: Programm online, Ticketverkauf gestartet", 116 | "link": "http://www.heise.de/developer/meldung/Continuous-Lifecycle-London-Programm-online-Ticketverkauf-gestartet-3086901.html?wt_mc=rss.developer.beitrag.atom", 117 | "pubDate": "2016-01-29T09:10:00.000Z", 118 | "content": "\n \n \n \"Continuous\n \n \n

Anfang Mai feiert die englische Version der Continuous-Lifecycle-Konferenz in London Premiere. Jez Humble und Dave Farley sind nur zwei der Sprecher aus dem nun bekannt gegebenen Programm, das Continuous Delivery, DevOps und Co. ins Zentrum stellt.

\n ", 119 | "contentSnippet": "Anfang Mai feiert die englische Version der Continuous-Lifecycle-Konferenz in London Premiere. Jez Humble und Dave Farley sind nur zwei der Sprecher aus dem nun bekannt gegebenen Programm, das Continuous Delivery, DevOps und Co. ins Zentrum stellt.", 120 | "summary": "Anfang Mai feiert die englische Version der Continuous-Lifecycle-Konferenz in London Premiere. Jez Humble und Dave Farley sind nur zwei der Sprecher aus dem nun bekannt gegebenen Programm, das Continuous Delivery, DevOps und Co. ins Zentrum stellt.", 121 | "id": "http://heise.de/-3086901", 122 | "isoDate": "2016-01-29T09:10:00.000Z" 123 | }, 124 | { 125 | "title": "Java Runtime Zing verdoppelt die maximale Speichergröße auf 2 TB", 126 | "link": "http://www.heise.de/developer/meldung/Java-Runtime-Zing-verdoppelt-die-maximale-Speichergroesse-auf-2-TB-3086807.html?wt_mc=rss.developer.beitrag.atom", 127 | "pubDate": "2016-01-29T08:58:00.000Z", 128 | "content": "\n \n \n \"Java\n \n \n

Die Java Virtual Machine Zing ist speziell auf speicherhungrige Anwendungen ausgelegt. Mit Version 16.1 darf der dynamische Speicher auf 2 Terabyte steigen.

\n ", 129 | "contentSnippet": "Die Java Virtual Machine Zing ist speziell auf speicherhungrige Anwendungen ausgelegt. Mit Version 16.1 darf der dynamische Speicher auf 2 Terabyte steigen.", 130 | "summary": "Die Java Virtual Machine Zing ist speziell auf speicherhungrige Anwendungen ausgelegt. Mit Version 16.1 darf der dynamische Speicher auf 2 Terabyte steigen.", 131 | "id": "http://heise.de/-3086807", 132 | "isoDate": "2016-01-29T08:58:00.000Z" 133 | }, 134 | { 135 | "title": "C# 7 – Stand der Dinge und Ausblick", 136 | "link": "http://www.heise.de/developer/artikel/C-7-Stand-der-Dinge-und-Ausblick-3086504.html?wt_mc=rss.developer.beitrag.atom", 137 | "pubDate": "2016-01-29T08:00:00.000Z", 138 | "content": "\n \n \n \"C#\n \n \n

Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt.

\n ", 139 | "contentSnippet": "Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt.", 140 | "summary": "Ein Blick auf die möglichen Neuerungen für C# 7 zeigt, wie sich die Sprache Anregungen jenseits des Tellerands der objektorientierten Programmierung holt.", 141 | "id": "http://heise.de/-3086504", 142 | "isoDate": "2016-01-29T08:00:00.000Z" 143 | }, 144 | { 145 | "title": "Apache Software Foundation bekommt ein neues Logo", 146 | "link": "http://www.heise.de/developer/meldung/Apache-Software-Foundation-bekommt-ein-neues-Logo-3086458.html?wt_mc=rss.developer.beitrag.atom", 147 | "pubDate": "2016-01-28T16:07:00.000Z", 148 | "content": "\n \n \n \"Apache\n \n \n

Das neue Logos soll zugleich die bisherige Vergangenheit und den zukunftsorientierten energischen Wachstum der Open-Source-Organisation reflektieren.

\n ", 149 | "contentSnippet": "Das neue Logos soll zugleich die bisherige Vergangenheit und den zukunftsorientierten energischen Wachstum der Open-Source-Organisation reflektieren.", 150 | "summary": "Das neue Logos soll zugleich die bisherige Vergangenheit und den zukunftsorientierten energischen Wachstum der Open-Source-Organisation reflektieren.", 151 | "id": "http://heise.de/-3086458", 152 | "isoDate": "2016-01-28T16:07:00.000Z" 153 | } 154 | ], 155 | "link": "http://www.heise.de/developer/", 156 | "feedUrl": "http://www.heise.de/developer/rss/news-atom.xml", 157 | "title": "heise developer neueste Meldungen", 158 | "lastBuildDate": "2016-02-01T17:54:50+01:00" 159 | } 160 | } -------------------------------------------------------------------------------- /test/output/heraldsun.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "The First Item", 6 | "link": "http://www.oreilly.com/example/001.html", 7 | "enclosure": { 8 | "url": "http://www.oreilly.com/001.mp3", 9 | "length": "54321", 10 | "type": "audio/mpeg" 11 | }, 12 | "content": "This is the first item.", 13 | "contentSnippet": "This is the first item.", 14 | "categories": [ 15 | { 16 | "_": "Business/Industries/Publishing/Publishers/Nonfiction/", 17 | "$": { 18 | "domain": "http://www.dmoz.org" 19 | } 20 | } 21 | ] 22 | }, 23 | { 24 | "title": "The Second Item", 25 | "link": "http://www.oreilly.com/example/002.html", 26 | "enclosure": { 27 | "url": "http://www.oreilly.com/002.mp3", 28 | "length": "54321", 29 | "type": "audio/mpeg" 30 | }, 31 | "content": "This is the second item.", 32 | "contentSnippet": "This is the second item.", 33 | "categories": [ 34 | { 35 | "_": "Business/Industries/Publishing/Publishers/Nonfiction/", 36 | "$": { 37 | "domain": "http://www.dmoz.org" 38 | } 39 | } 40 | ] 41 | } 42 | ], 43 | "title": "RSS0.92 Example", 44 | "description": "This is an example RSS0.91 feed", 45 | "pubDate": "03 Apr 02 1500 GMT", 46 | "webMaster": "webmaster@oreilly.com", 47 | "managingEditor": "editor@oreilly.com", 48 | "link": "http://www.oreilly.com/example/index.html", 49 | "language": "en-gb", 50 | "copyright": "Copyright 2002, Oreilly and Associates.", 51 | "lastBuildDate": "03 Apr 02 1500 GMT", 52 | "rating": "5" 53 | } 54 | } -------------------------------------------------------------------------------- /test/output/incomplete-fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | {} 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /test/output/instant-article.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "creator": "tobi", 6 | "date": "2016-05-04T06:53:45Z", 7 | "title": "My first Instant Article", 8 | "link": "https://localhost:8000", 9 | "pubDate": "Wed, 04 May 2016 06:53:45 GMT", 10 | "content:encoded": "Lorem ipsum", 11 | "content:encodedSnippet": "Lorem ipsum", 12 | "dc:creator": "tobi", 13 | "dc:date": "2016-05-04T06:53:45Z", 14 | "content": "Lorem ipsum", 15 | "contentSnippet": "Lorem ipsum", 16 | "guid": "https://localhost:8000", 17 | "isoDate": "2016-05-04T06:53:45.000Z" 18 | } 19 | ], 20 | "title": "Instant Article Test", 21 | "description": "1, 2, 1, 2… check the mic!", 22 | "pubDate": "Fri, 13 May 2016 15:14:05 GMT", 23 | "link": "https://localhost:8000", 24 | "language": "en" 25 | } 26 | } -------------------------------------------------------------------------------- /test/output/item-itunes-episodeType.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "Trailer", 6 | "link": "https://baywatch-berlin.podigee.io/t1-trailer", 7 | "pubDate": "Mon, 18 Nov 2019 19:14:32 +0000", 8 | "content:encoded": "\n Baywatch Berlin\nBaywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen.\n ", 9 | "content:encodedSnippet": "Baywatch Berlin\nBaywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen.", 10 | "enclosure": { 11 | "url": "https://cdn.podigee.com/media/podcast_16544_baywatch_berlin_episode_1_trailer.mp3?v=1574079359&source=feed", 12 | "type": "audio/mpeg", 13 | "length": "2338520" 14 | }, 15 | "content": "Baywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen.", 16 | "contentSnippet": "Baywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen.", 17 | "guid": "cc973b648d6fb41e4e5ef7e4b4ee7604", 18 | "isoDate": "2019-11-18T19:14:32.000Z", 19 | "itunes": { 20 | "author": "Klaas Heufer-Umlauf, Thomas Schmitt und Jakob Lundt", 21 | "subtitle": "Baywatch Berlin", 22 | "summary": "Baywatch Berlin startet am 22. November. Mit Klaas Heufer-Umlauf und den anderen.", 23 | "explicit": "no", 24 | "duration": "114", 25 | "image": "https://images.podigee-cdn.net/0x,sIupQrkLl-L2GPJE7IKzGC90ZhL3wmVuFpBuBNTf2TdQ=/https://cdn.podigee.com/uploads/u10314/6fd157b2-442d-4685-b78b-3bac68f0517f.jpg", 26 | "episode": "1", 27 | "keywords": "klaas,baywatch,berlin,late night berlin,joko und klaas,duell um die welt", 28 | "episodeType": "trailer" 29 | } 30 | } 31 | ], 32 | "feedUrl": "https://pubsubhubbub.appspot.com/", 33 | "image": { 34 | "link": "https://baywatch-berlin.podigee.io/", 35 | "url": "https://images.podigee-cdn.net/0x,sKl1jBkJzQhalasSZk-t081rvkZ1jaMq35213ibRTr2U=/https://cdn.podigee.com/uploads/u10314/70a0b394-56a9-47ad-883e-38493268d6bb.jpg", 36 | "title": "Baywatch Berlin" 37 | }, 38 | "paginationLinks": { 39 | "self": "https://baywatch-berlin.podigee.io/feed/mp3", 40 | "first": "https://baywatch-berlin.podigee.io/feed/mp3", 41 | "last": "https://baywatch-berlin.podigee.io/feed/mp3?page=1" 42 | }, 43 | "title": "Baywatch Berlin", 44 | "description": "Das Beautiful Mind Klaas Heufer-Umlauf probiert in diesem Podcast nach über 10 Jahren weltfremden Jet Set Spaß Kontakt zur echten Welt aufzunehmen. \nWie einst in der Weihnachtsgeschichte wird er dabei von seinen Freunden Thomas Schmitt und Jakob Lundt an die Hand genommen und langsam wieder mit den Themen des wahren Lebens in Kontakt gebracht. Tauchen Sie ein und seien Sie die Kugel im Flipperautomaten „Baywatch Berlin“!", 45 | "pubDate": "Mon, 18 Nov 2019 19:19:26 +0000", 46 | "generator": "Podigee (https://podigee.com)", 47 | "link": "https://baywatch-berlin.podigee.io/", 48 | "language": "de", 49 | "lastBuildDate": "Fri, 18 Jun 2021 09:57:01 +0000", 50 | "itunes": { 51 | "owner": { 52 | "name": "Klaas Heufer-Umlauf, Thomas Schmitt und Jakob Lundt", 53 | "email": "alexander.krawczyk@starwatch.de" 54 | }, 55 | "image": "https://images.podigee-cdn.net/0x,sKl1jBkJzQhalasSZk-t081rvkZ1jaMq35213ibRTr2U=/https://cdn.podigee.com/uploads/u10314/70a0b394-56a9-47ad-883e-38493268d6bb.jpg", 56 | "categories": [ 57 | "Comedy", 58 | "Society & Culture", 59 | "TV & Film" 60 | ], 61 | "categoriesWithSubs": [ 62 | { 63 | "name": "Comedy", 64 | "subs": null 65 | }, 66 | { 67 | "name": "Society & Culture", 68 | "subs": null 69 | }, 70 | { 71 | "name": "TV & Film", 72 | "subs": null 73 | } 74 | ], 75 | "keywords": [ 76 | "klaas", 77 | "late night berlin", 78 | "klaas heufer umlauf", 79 | "baywatch berlin", 80 | "baywatch" 81 | ], 82 | "author": "Klaas Heufer-Umlauf, Thomas Schmitt und Jakob Lundt", 83 | "subtitle": "", 84 | "summary": "Das Beautiful Mind Klaas Heufer-Umlauf probiert in diesem Podcast nach über 10 Jahren weltfremden Jet Set Spaß Kontakt zur echten Welt aufzunehmen. \nWie einst in der Weihnachtsgeschichte wird er dabei von seinen Freunden Thomas Schmitt und Jakob Lundt an die Hand genommen und langsam wieder mit den Themen des wahren Lebens in Kontakt gebracht. Tauchen Sie ein und seien Sie die Kugel im Flipperautomaten „Baywatch Berlin“!", 85 | "explicit": "no" 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /test/output/itunes-category.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "creator": "Taverncast: www.taverncast.com", 6 | "title": "Taverncast 62 - Temporal Anomaly", 7 | "link": "http://taverncast.com/", 8 | "pubDate": "07 Nov 2015 12:00:00 EST", 9 | "author": "Taverncast", 10 | "content:encoded": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 11 | "content:encodedSnippet": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 12 | "enclosure": { 13 | "url": "http://www.podtrac.com/pts/redirect.mp3?http://www.taverncast.com/shows/taverncast-62.mp3", 14 | "length": "47160424", 15 | "type": "audio/mpeg" 16 | }, 17 | "dc:creator": "Taverncast: www.taverncast.com", 18 | "comments": "http://www.taverncast.com", 19 | "content": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 20 | "contentSnippet": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 21 | "guid": "http://taverncast.com/shows/taverncast-62.mp3", 22 | "categories": [ 23 | "Society & Culture" 24 | ], 25 | "isoDate": "2015-11-07T17:00:00.000Z", 26 | "itunes": { 27 | "author": "Taverncast", 28 | "subtitle": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 29 | "summary": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 30 | "explicit": "no", 31 | "duration": "1:09:50", 32 | "keywords": "Taverncast, beer, games, hobbies, gaming, computer, geek, party, movies, comics, pop-culture, weird, comedy, humor, funny, talk, nerd, news, politics, paranormal, conspiracy, morning show, stone brewing company, time travel, back to the future, past, future, present, timeline, temporal, travel, time, dog, doodle, poodle, apple, laptop, computer, arrogant, bastard, arrogant bastard" 33 | } 34 | } 35 | ], 36 | "image": { 37 | "link": "http://www.taverncast.com/", 38 | "url": "http://www.taverncast.com/itunes-logo.png", 39 | "title": "Taverncast - Happy Hour in Your Head - Since 2005", 40 | "width": "144", 41 | "height": "300" 42 | }, 43 | "title": "Taverncast - Happy Hour in Your Head - Since 2005", 44 | "description": "Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head!", 45 | "pubDate": "Wed, 03 Aug 2005 18:00:00 GMT", 46 | "managingEditor": "taverncast@taverncast.com", 47 | "generator": "me mitts", 48 | "link": "http://www.taverncast.com/", 49 | "language": "en-us", 50 | "copyright": "Taverncast, 2015", 51 | "itunes": { 52 | "owner": { 53 | "name": "Taverncast", 54 | "email": "taverncast@taverncast.com" 55 | }, 56 | "categories": [ 57 | "Society & Culture", 58 | "Games & Hobbies" 59 | ], 60 | "categoriesWithSubs": [ 61 | { 62 | "name": "Society & Culture", 63 | "subs": null 64 | }, 65 | { 66 | "name": "Games & Hobbies", 67 | "subs": [ 68 | { 69 | "name": "Hobbies" 70 | } 71 | ] 72 | } 73 | ], 74 | "author": "Bryce Erwin, Bill Ticknor, Michelle O'Neill, Mike Monan, Aric Watson, Jennifer Albrecht, Lauren Hoban and Derek Chew", 75 | "subtitle": "Beer - Talk - Fun. It's Happy Hour in Your Head!", 76 | "summary": "Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head!", 77 | "explicit": "no" 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /test/output/itunes-href.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "creator": "\nTransfermarkt", 6 | "title": "\nManager bewertet Transfers | Eberl lobt BVB für Pulisic-Deal – Hudson-Odoi kostet „fast so viel wie mein Kader“", 7 | "link": "\nhttp://www.transfermarkt.de/eberl-lobt-bvb-fur-pulisic-deal-ndash-hudson-odoi-kostet-bdquo-fast-so-viel-wie-mein-kader-ldquo-/view/news/326649?rss", 8 | "pubDate": "\nMon, 07 Jan 2019 17:19:19 +0100", 9 | "author": "\nTransfermarkt", 10 | "content": "\nMax Eberl (Foto) hat dem BVB zum Transfererlös von Christian Pulisic (20) gratuliert. Gleichzeitig prangerte Gladbachs Sportdirektor allerdings auch derart hohe Ablösesummen an....\n", 11 | "contentSnippet": "Max Eberl (Foto) hat dem BVB zum Transfererlös von Christian Pulisic (20) gratuliert. Gleichzeitig prangerte Gladbachs Sportdirektor allerdings auch derart hohe Ablösesummen an....", 12 | "guid": "\nhttp://www.transfermarkt.de/eberl-lobt-bvb-fur-pulisic-deal-ndash-hudson-odoi-kostet-bdquo-fast-so-viel-wie-mein-kader-ldquo-/view/news/326649?rss", 13 | "isoDate": "2019-01-07T16:19:19.000Z" 14 | }, 15 | { 16 | "creator": "\nTransfermarkt", 17 | "title": "\nIm zweiten Anlauf | Magdeburg verleiht Talent Harant – Fünfter FCM-Spieler in Halberstadt", 18 | "link": "\nhttp://www.transfermarkt.de/magdeburg-verleiht-talent-harant-ndash-funfter-fcm-spieler-in-halberstadt/view/news/326660?rss", 19 | "pubDate": "\nMon, 07 Jan 2019 16:55:00 +0100", 20 | "author": "\nTransfermarkt", 21 | "content": "\nDer 1.FC Magdeburg treibt seine Umstrukturierung des Kaders im Winter-Transferfenster weiter voran. Nachdem Jan Kirchhoff und Timo Perthel den Kader von Trainer Michael Oenning verstärkten und...\n", 22 | "contentSnippet": "Der 1.FC Magdeburg treibt seine Umstrukturierung des Kaders im Winter-Transferfenster weiter voran. Nachdem Jan Kirchhoff und Timo Perthel den Kader von Trainer Michael Oenning verstärkten und...", 23 | "guid": "\nhttp://www.transfermarkt.de/magdeburg-verleiht-talent-harant-ndash-funfter-fcm-spieler-in-halberstadt/view/news/326660?rss", 24 | "isoDate": "2019-01-07T15:55:00.000Z" 25 | }, 26 | { 27 | "creator": "\nTransfermarkt", 28 | "title": "\nTransfer fix | Iborra zurück nach Spanien: Villarreal plant langfristig mit Leicesters Reservisten", 29 | "link": "\nhttp://www.transfermarkt.de/iborra-zuruck-nach-spanien-villarreal-plant-langfristig-mit-leicesters-reservisten/view/news/326658?rss", 30 | "pubDate": "\nMon, 07 Jan 2019 16:29:39 +0100", 31 | "author": "\nTransfermarkt", 32 | "content": "\nDer FC Villarreal hat sich mit Leicester City geeinigt und die Dienste von Vicente Iborra (30, Foto) gesichert. Bei den „Foxes“ stand der Mittelfeldspieler seit 2017 unter Vertrag, kam...\n", 33 | "contentSnippet": "Der FC Villarreal hat sich mit Leicester City geeinigt und die Dienste von Vicente Iborra (30, Foto) gesichert. Bei den „Foxes“ stand der Mittelfeldspieler seit 2017 unter Vertrag, kam...", 34 | "guid": "\nhttp://www.transfermarkt.de/iborra-zuruck-nach-spanien-villarreal-plant-langfristig-mit-leicesters-reservisten/view/news/326658?rss", 35 | "isoDate": "2019-01-07T15:29:39.000Z" 36 | }, 37 | { 38 | "creator": "\nTransfermarkt", 39 | "title": "\nJetzt bei TM.tv | BVB-Interesse an Werner: Philipp im Gegenzug nach Leipzig?", 40 | "link": "\nhttp://www.transfermarkt.de/bvb-interesse-an-werner-philipp-im-gegenzug-nach-leipzig-/view/news/326652?rss", 41 | "pubDate": "\nMon, 07 Jan 2019 16:00:00 +0100", 42 | "author": "\nTransfermarkt", 43 | "content": "\nTauschen Timo Werner und Maximilian Philipp die Klubs? Nach dem „Kicker“ berichtet nun auch die „Bild“ vom Interesse des BVB an Leipzigs Werner (Foto). Zudem wird berichtet,...\n", 44 | "contentSnippet": "Tauschen Timo Werner und Maximilian Philipp die Klubs? Nach dem „Kicker“ berichtet nun auch die „Bild“ vom Interesse des BVB an Leipzigs Werner (Foto). Zudem wird berichtet,...", 45 | "guid": "\nhttp://www.transfermarkt.de/bvb-interesse-an-werner-philipp-im-gegenzug-nach-leipzig-/view/news/326652?rss", 46 | "isoDate": "2019-01-07T15:00:00.000Z" 47 | }, 48 | { 49 | "creator": "\nTransfermarkt", 50 | "title": "\nRechtsverteidiger | Dritter Wintertransfer: Däne Paulsen soll Ingolstadt aus der Misere helfen", 51 | "link": "\nhttp://www.transfermarkt.de/dritter-wintertransfer-dane-paulsen-soll-ingolstadt-aus-der-misere-helfen/view/news/326654?rss", 52 | "pubDate": "\nMon, 07 Jan 2019 15:46:15 +0100", 53 | "author": "\nTransfermarkt", 54 | "content": "\nDer FC Ingolstadt hat Björn Paulsen (Foto) als seinen dritten Neuzugang in diesem Winter präsentiert. Der strauchelnde Zweitligist stattete den dänischen Rechtsverteidiger mit einem...\n", 55 | "contentSnippet": "Der FC Ingolstadt hat Björn Paulsen (Foto) als seinen dritten Neuzugang in diesem Winter präsentiert. Der strauchelnde Zweitligist stattete den dänischen Rechtsverteidiger mit einem...", 56 | "guid": "\nhttp://www.transfermarkt.de/dritter-wintertransfer-dane-paulsen-soll-ingolstadt-aus-der-misere-helfen/view/news/326654?rss", 57 | "isoDate": "2019-01-07T14:46:15.000Z" 58 | }, 59 | { 60 | "creator": "\nTransfermarkt", 61 | "title": "\nVertrag mit Option | Baffo darf sich bis Sommer in Duisburg beweisen – „Wir mussten reagieren“", 62 | "link": "\nhttp://www.transfermarkt.de/baffo-darf-sich-bis-sommer-in-duisburg-beweisen-ndash-bdquo-wir-mussten-reagieren-ldquo-/view/news/326647?rss", 63 | "pubDate": "\nMon, 07 Jan 2019 15:19:03 +0100", 64 | "author": "\nTransfermarkt", 65 | "content": "\nZweitligist MSV Duisburg hat den vereinslosen Joseph Baffo (Foto) mit einem Vertrag bis Saisonende ausgestattet. Der 26-jährige Innenverteidiger war zuletzt zur Probe im Training der...\n", 66 | "contentSnippet": "Zweitligist MSV Duisburg hat den vereinslosen Joseph Baffo (Foto) mit einem Vertrag bis Saisonende ausgestattet. Der 26-jährige Innenverteidiger war zuletzt zur Probe im Training der...", 67 | "guid": "\nhttp://www.transfermarkt.de/baffo-darf-sich-bis-sommer-in-duisburg-beweisen-ndash-bdquo-wir-mussten-reagieren-ldquo-/view/news/326647?rss", 68 | "isoDate": "2019-01-07T14:19:03.000Z" 69 | }, 70 | { 71 | "creator": "\nTransfermarkt", 72 | "title": "\nNach Haidara-Transfer | Bericht: Salzburg-Führung verhinderte Wolf-Wechsel nach Leipzig – Veto von Rose", 73 | "link": "\nhttp://www.transfermarkt.de/bericht-salzburg-fuhrung-verhinderte-wolf-wechsel-nach-leipzig-ndash-veto-von-rose/view/news/326640?rss", 74 | "pubDate": "\nMon, 07 Jan 2019 14:47:00 +0100", 75 | "author": "\nTransfermarkt", 76 | "content": "\nRB Leipzig ist einem Bericht zufolge bei dem Versuch gescheitert, Hannes Wolf (Foto) vom Schwester-Klub RB Salzburg nach Sachsen zu lotsen. Das meldeten die „Salzburger Nachrichten“ mit...\n", 77 | "contentSnippet": "RB Leipzig ist einem Bericht zufolge bei dem Versuch gescheitert, Hannes Wolf (Foto) vom Schwester-Klub RB Salzburg nach Sachsen zu lotsen. Das meldeten die „Salzburger Nachrichten“ mit...", 78 | "guid": "\nhttp://www.transfermarkt.de/bericht-salzburg-fuhrung-verhinderte-wolf-wechsel-nach-leipzig-ndash-veto-von-rose/view/news/326640?rss", 79 | "isoDate": "2019-01-07T13:47:00.000Z" 80 | }, 81 | { 82 | "creator": "\nTransfermarkt", 83 | "title": "\nWechsel in die Regionalliga | Regensburg verabschiedet „verdienten Jahn-Profi“ Kopp nach Bayreuth", 84 | "link": "\nhttp://www.transfermarkt.de/regensburg-verabschiedet-bdquo-verdienten-jahn-profi-ldquo-kopp-nach-bayreuth/view/news/326639?rss", 85 | "pubDate": "\nMon, 07 Jan 2019 14:15:21 +0100", 86 | "author": "\nTransfermarkt", 87 | "content": "\nDefensiv-Allrounder Sven Kopp (23, Foto) verlässt den SSV Jahn Regensburg nach viereinhalb Jahren. Der Klub verkündete am Montag, dass der „verdiente Jahn-Profi“ zur SpVgg...\n", 88 | "contentSnippet": "Defensiv-Allrounder Sven Kopp (23, Foto) verlässt den SSV Jahn Regensburg nach viereinhalb Jahren. Der Klub verkündete am Montag, dass der „verdiente Jahn-Profi“ zur SpVgg...", 89 | "guid": "\nhttp://www.transfermarkt.de/regensburg-verabschiedet-bdquo-verdienten-jahn-profi-ldquo-kopp-nach-bayreuth/view/news/326639?rss", 90 | "isoDate": "2019-01-07T13:15:21.000Z" 91 | }, 92 | { 93 | "creator": "\nTransfermarkt", 94 | "title": "\nRegionalliga West | Zschiesche übernimmt Trainerposten beim Bonner SC", 95 | "link": "\nhttp://www.transfermarkt.de/zschiesche-ubernimmt-trainerposten-beim-bonner-sc/view/news/326633?rss", 96 | "pubDate": "\nMon, 07 Jan 2019 14:01:09 +0100", 97 | "author": "\nTransfermarkt", 98 | "content": "\nMarkus Zschiesche (Foto) übernimmt das Traineramt beim West-Regionalligisten Bonner SC. Der 36-Jährige stand vergangene Saison für den Berliner AK 07 an der Seitenlinie und...\n", 99 | "contentSnippet": "Markus Zschiesche (Foto) übernimmt das Traineramt beim West-Regionalligisten Bonner SC. Der 36-Jährige stand vergangene Saison für den Berliner AK 07 an der Seitenlinie und...", 100 | "guid": "\nhttp://www.transfermarkt.de/zschiesche-ubernimmt-trainerposten-beim-bonner-sc/view/news/326633?rss", 101 | "isoDate": "2019-01-07T13:01:09.000Z" 102 | }, 103 | { 104 | "creator": "\nTransfermarkt", 105 | "title": "\nChelsea plant Angebot | Cagliari erwartet Rekordablöse für Barella: Teuerster Italiener nach Jorginho & Buffon?", 106 | "link": "\nhttp://www.transfermarkt.de/cagliari-erwartet-rekordablose-fur-barella-teuerster-italiener-nach-jorginho-amp-buffon-/view/news/326629?rss", 107 | "pubDate": "\nMon, 07 Jan 2019 13:51:00 +0100", 108 | "author": "\nTransfermarkt", 109 | "content": "\nAbseits der italienischen Top-Klubs hat sich in den vergangenen Monaten Nicolò Barella (Foto) zu einem der Shootingstars der italienischen Serie A entwickelt. Das 21-jährige...\n", 110 | "contentSnippet": "Abseits der italienischen Top-Klubs hat sich in den vergangenen Monaten Nicolò Barella (Foto) zu einem der Shootingstars der italienischen Serie A entwickelt. Das 21-jährige...", 111 | "guid": "\nhttp://www.transfermarkt.de/cagliari-erwartet-rekordablose-fur-barella-teuerster-italiener-nach-jorginho-amp-buffon-/view/news/326629?rss", 112 | "isoDate": "2019-01-07T12:51:00.000Z" 113 | } 114 | ], 115 | "image": { 116 | "link": "http://www.transfermarkt.de", 117 | "url": "http://www.transfermarkt.de/images/logo.png", 118 | "title": "Transfermarkt" 119 | }, 120 | "title": "Transfermarkt", 121 | "description": "Die 10 letzten News\n", 122 | "pubDate": "Mon, 07 Jan 2019 18:12:17 +0100", 123 | "link": "http://www.transfermarkt.de/rss/news", 124 | "language": "de-de" 125 | } 126 | } -------------------------------------------------------------------------------- /test/output/itunes-keywords-array.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "Ostafrika stellt sich auf die nächste Heuschreckenplage ein", 6 | "link": "https://www.swr.de/swr2/wissen/Hungersnot-Ostafrika-stellt-sich-auf-die-naechste-Heuschreckenplage-ein,ostafrika-stellt-sich-auf-naechste-heuschreckenplage-ein-100.html", 7 | "pubDate": "Tue, 10 Mar 2020 16:05:00 +0100", 8 | "enclosure": { 9 | "length": "4668354", 10 | "type": "audio/mpeg", 11 | "url": "https://avdlswr-a.akamaihd.net/swr/swr2/wissen/impuls/beitraege/2020/03/ostafrika-droht-die-naechste-heuschreckenplage.m.mp3" 12 | }, 13 | "content": "Nach der jüngsten Heuschreckenplage droht Ostafrika neues Unheil. Es regnet mehr als sonst. Der feuchte Boden ist ein idealer Brutkasten für die nächste Generation Heuschrecken. Die ohnehin angespannte Ernährungslage könnte sich weiter verschlechtern.", 14 | "contentSnippet": "Nach der jüngsten Heuschreckenplage droht Ostafrika neues Unheil. Es regnet mehr als sonst. Der feuchte Boden ist ein idealer Brutkasten für die nächste Generation Heuschrecken. Die ohnehin angespannte Ernährungslage könnte sich weiter verschlechtern.", 15 | "guid": "c88fbba9-c228-4724-bf41-8c193b72491c", 16 | "categories": [ 17 | "Wissen,Heuschrecken,Heuschreckenschwärme,Heuschreckenplage,Ostafrika,Natur" 18 | ], 19 | "isoDate": "2020-03-10T15:05:00.000Z", 20 | "itunes": { 21 | "duration": "05:09", 22 | "image": "https://www.swr.de/swr2/wissen/Heuschreckeninvasion-Hochland-von-Madagaskar-Afrika,1583836906080,swr2-impuls-heuschrecken-plage-afrika-100~_v-1x1@2dXL_-1f32c27c4978132dd0854e53b5ed30e10facc189.jpg" 23 | } 24 | } 25 | ], 26 | "feedUrl": "https://www.swr.de/~podcast/swr2/programm/SWR2-Wissen-Impuls-Podcast,swr2-impuls-podcast-100.xml", 27 | "image": { 28 | "link": "https://www.swr.de/swr2/programm/SWR2-Wissen-Impuls-Podcast,swr2-impuls-podcast-100.html", 29 | "url": "https://www.swr.de/swr2/programm/Podcastbild-SWR2-Impuls,1564741489275,swr2-impuls-podcast-104~_v-16x9@2dS_-6be50a9c75559ca1aaf1d0b25bae287afdcd877a.jpg", 30 | "title": "SWR2 Impuls - Wissen aktuell" 31 | }, 32 | "paginationLinks": { 33 | "self": "https://www.swr.de/~podcast/swr2/programm/SWR2-Wissen-Impuls-Podcast,swr2-impuls-podcast-100.xml" 34 | }, 35 | "title": "SWR2 Impuls - Wissen aktuell", 36 | "description": "Neues aus Wissenschaft, Medizin, Umwelt und Bildung. ", 37 | "pubDate": "Wed, 9 Oct 2019 16:39:00 +0200", 38 | "link": "https://www.swr.de/swr2/programm/SWR2-Wissen-Impuls-Podcast,swr2-impuls-podcast-100.html", 39 | "language": "de", 40 | "copyright": "Copyright © Südwestrundfunk", 41 | "itunes": { 42 | "owner": { 43 | "name": "Südwestrundfunk", 44 | "email": "podcast@swr.de" 45 | }, 46 | "image": "https://www.swr.de/swr2/programm/Podcastbild-SWR2-Impuls,1564741489275,swr2-impuls-podcast-104~_v-1x1@2dXL_-1f32c27c4978132dd0854e53b5ed30e10facc189.jpg", 47 | "keywords": [ 48 | "Gesundheit und Mensch", 49 | "Wissenschaft und Forschung", 50 | "Wissen", 51 | "Wissenschaft", 52 | "Impuls", 53 | "Podcast", 54 | "Download" 55 | ], 56 | "author": "SWR", 57 | "subtitle": "Neues aus Wissenschaft, Medizin, Umwelt und Bildung. Dazu Musik, die sich vom Mainstream abhebt. Montags bis freitags um 16:05. ", 58 | "summary": "Neues aus Wissenschaft, Medizin, Umwelt und Bildung. ", 59 | "explicit": "No" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /test/output/itunes-keywords.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "creator": "Taverncast: www.taverncast.com", 6 | "title": "Taverncast 62 - Temporal Anomaly", 7 | "link": "http://taverncast.com/", 8 | "pubDate": "07 Nov 2015 12:00:00 EST", 9 | "author": "Taverncast", 10 | "content:encoded": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 11 | "content:encodedSnippet": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 12 | "enclosure": { 13 | "url": "http://www.podtrac.com/pts/redirect.mp3?http://www.taverncast.com/shows/taverncast-62.mp3", 14 | "length": "47160424", 15 | "type": "audio/mpeg" 16 | }, 17 | "dc:creator": "Taverncast: www.taverncast.com", 18 | "comments": "http://www.taverncast.com", 19 | "content": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 20 | "contentSnippet": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 21 | "guid": "http://taverncast.com/shows/taverncast-62.mp3", 22 | "categories": [ 23 | "Society & Culture" 24 | ], 25 | "isoDate": "2015-11-07T17:00:00.000Z", 26 | "itunes": { 27 | "author": "Taverncast", 28 | "subtitle": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 29 | "summary": "Taverncast tosses around the age old topic: time travel! Where would you go? Why would you go? Also, Bryce has customer service issues, and Mike buys a dog.", 30 | "explicit": "no", 31 | "duration": "1:09:50", 32 | "keywords": "Taverncast, beer, games, hobbies, gaming, computer, geek, party, movies, comics, pop-culture, weird, comedy, humor, funny, talk, nerd, news, politics, paranormal, conspiracy, morning show, stone brewing company, time travel, back to the future, past, future, present, timeline, temporal, travel, time, dog, doodle, poodle, apple, laptop, computer, arrogant, bastard, arrogant bastard" 33 | } 34 | } 35 | ], 36 | "image": { 37 | "link": "http://www.taverncast.com/", 38 | "url": "http://www.taverncast.com/itunes-logo.png", 39 | "title": "Taverncast - Happy Hour in Your Head - Since 2005", 40 | "width": "144", 41 | "height": "300" 42 | }, 43 | "title": "Taverncast - Happy Hour in Your Head - Since 2005", 44 | "description": "Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head!", 45 | "pubDate": "Wed, 03 Aug 2005 18:00:00 GMT", 46 | "managingEditor": "taverncast@taverncast.com", 47 | "generator": "me mitts", 48 | "link": "http://www.taverncast.com/", 49 | "language": "en-us", 50 | "copyright": "Taverncast, 2015", 51 | "itunes": { 52 | "owner": { 53 | "name": "Taverncast", 54 | "email": "taverncast@taverncast.com" 55 | }, 56 | "keywords": [ 57 | "Culture", 58 | "Society" 59 | ], 60 | "author": "Bryce Erwin, Bill Ticknor, Michelle O'Neill, Mike Monan, Aric Watson, Jennifer Albrecht, Lauren Hoban and Derek Chew", 61 | "subtitle": "Beer - Talk - Fun. It's Happy Hour in Your Head!", 62 | "summary": "Order a beer, pull up a seat and join your friends at Taverncast for our patented un-scripted roundtable discussions, covering a wide range of topics with a unique wit and inviting style - all blended together, it's Happy Hour in Your Head!", 63 | "explicit": "no" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /test/output/missing-fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "id": "tag:github.com,2008:Repository/11167738/v3.9.0" 6 | } 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /test/output/narro.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "creator": "foobar@gmail.com", 6 | "title": "FAQ for Narro", 7 | "link": "https://www.narro.co/article/54e703933058540300000069", 8 | "pubDate": "Fri, 20 Feb 2015 09:51:15 UTC", 9 | "author": "foobar@gmail.com", 10 | "enclosure": { 11 | "url": "https://s3.amazonaws.com/nareta-articles/audio/54d046c293f79c0300000003/7e2d2b00-a945-441a-f49b-063786a319a4.mp3", 12 | "length": "74", 13 | "type": "audio/mpeg" 14 | }, 15 | "content": "Listen to your reading list anywhere. Narro will take your bookmarked articles and read them back to you as a podcast.
http://www.narro.co/faq
    ", 16 | "contentSnippet": "Listen to your reading list anywhere. Narro will take your bookmarked articles and read them back to you as a podcast.\n http://www.narro.co/faq", 17 | "guid": "https://www.narro.co/article/54e703933058540300000069", 18 | "isoDate": "2015-02-20T09:51:15.000Z", 19 | "itunes": { 20 | "author": "foobar@gmail.com", 21 | "subtitle": "FAQ for Narro", 22 | "summary": "Listen to your reading list anywhere. Narro will take your bookmarked articles and read them back to you as a podcast. ... http://www.narro.co/faq ...
      ", 23 | "explicit": "no", 24 | "duration": "74", 25 | "image": "http://example.com/someImage.jpg" 26 | } 27 | } 28 | ], 29 | "feedUrl": "http://on.narro.co/f", 30 | "paginationLinks": { 31 | "self": "http://on.narro.co/f" 32 | }, 33 | "title": "foobar on Narro", 34 | "description": "foobar uses Narro to create a podcast of articles transcribed to audio.", 35 | "pubDate": "Fri, 08 Jul 2016 13:40:00 UTC", 36 | "webMaster": "josh@narro.co", 37 | "managingEditor": "foobar@gmail.com", 38 | "generator": "gopod - http://github.com/jbckmn/gopod", 39 | "link": "http://on.narro.co/f", 40 | "language": "en", 41 | "copyright": "All article content copyright of respective source authors.", 42 | "ttl": "20", 43 | "itunes": { 44 | "owner": { 45 | "name": "foobar", 46 | "email": "foobar@gmail.com" 47 | }, 48 | "image": "https://www.narro.co/images/narro-icon-lg.png", 49 | "author": "foobar@gmail.com", 50 | "subtitle": "foobar uses Narro to create a podcast of articles transcribed to audio.", 51 | "summary": "foobar uses Narro to create a podcast of articles transcribed to audio.", 52 | "explicit": "no" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/output/pagination-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "The First Episode", 6 | "link": "https://example.test/episode?id=283843", 7 | "pubDate": "Thu, 21 Jan 2021 18:58:00 +1100", 8 | "enclosure": { 9 | "url": "https://example.test/test-audio.mp3", 10 | "length": "38068096", 11 | "type": "audio/mpeg" 12 | }, 13 | "content": "
      The First Episode
      ", 14 | "contentSnippet": "The First Episode", 15 | "guid": "a6f22abe-be5c-4a37-8f4b-8be3d69b0235", 16 | "isoDate": "2021-01-21T07:58:00.000Z", 17 | "itunes": { 18 | "author": "Steve Thompson", 19 | "subtitle": "The First Episode...", 20 | "summary": "The First Episode", 21 | "explicit": "No", 22 | "duration": "39:27", 23 | "image": "https://example.test/test-image.jpg" 24 | } 25 | } 26 | ], 27 | "feedUrl": "http://example.org/index.atom?page=3", 28 | "image": { 29 | "link": "https://example.test/shows/new-show-1", 30 | "url": "https://example.test/test-image.jpg", 31 | "title": "New Show" 32 | }, 33 | "paginationLinks": { 34 | "self": "http://example.org/index.atom?page=3", 35 | "first": "http://example.org/index.atom", 36 | "next": "http://example.org/index.atom?page=4", 37 | "prev": "http://example.org/index.atom?page=2", 38 | "last": "http://example.org/index.atom?page=5" 39 | }, 40 | "title": "New Show - With Episode Block", 41 | "description": "New Show", 42 | "link": "https://example.test/shows/new-show-1", 43 | "language": "en-us", 44 | "lastBuildDate": "Tue, 31 Mar 2020 05:19:30 +0000", 45 | "itunes": { 46 | "owner": { 47 | "name": "Steve Thompson", 48 | "email": "steve.t@gmail.com" 49 | }, 50 | "image": "https://example.test/test-image.jpg", 51 | "categories": [ 52 | "Arts" 53 | ], 54 | "categoriesWithSubs": [ 55 | { 56 | "name": "Arts", 57 | "subs": null 58 | } 59 | ], 60 | "author": "Steve Thompson", 61 | "subtitle": "New Show", 62 | "summary": "New Show", 63 | "explicit": "No" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /test/output/uolNoticias.json: -------------------------------------------------------------------------------- 1 | { 2 | "feed": { 3 | "items": [ 4 | { 5 | "title": "Ibope: Bolsonaro perde de Haddad, Ciro e Alckmin em simula��es de 2� turno", 6 | "link": "https://noticias.uol.com.br/politica/eleicoes/2018/noticias/2018/09/24/ibope-bolsonaro-perde-de-haddad-ciro-e-alckmin-em-simulacoes-de-2-turno.htm", 7 | "pubDate": "Seg, 24 Set 2018 19:42:40 -0300", 8 | "content": " \tL�der na pesquisa Ibope de inten��o de voto para o primeiro turno divulgada nesta segunda-feira (24), o candidato do PSL a presidente, Jair Bolsonaro, n�o tem o mesmo desempenho nas simula��es de segundo turno feitas pela empresa no mesmo levantamento. ", 9 | "contentSnippet": "L�der na pesquisa Ibope de inten��o de voto para o primeiro turno divulgada nesta segunda-feira (24), o candidato do PSL a presidente, Jair Bolsonaro, n�o tem o mesmo desempenho nas simula��es de segundo turno feitas pela empresa no mesmo levantamento." 10 | }, 11 | { 12 | "title": "Promotoria abre inqu�rito para apurar suspeita de improbidade de Skaf no Sebrae-SP", 13 | "link": "https://www1.folha.uol.com.br/poder/2018/09/promotoria-abre-inquerito-para-apurar-suspeita-de-improbidade-de-skaf-no-sebrae-sp.shtml", 14 | "pubDate": "Seg, 24 Set 2018 19:38:00 -0300", 15 | "content": "", 16 | "contentSnippet": "" 17 | }, 18 | { 19 | "title": "Apucarana garante continuidade do Pr� Aprendiz; ano vai fechar�com 13 mil pessoas qualificadas", 20 | "link": "https://tnonline.uol.com.br/noticias/apucarana/45,470927,24,09,apucarana-garante-continuidade-do-pre-aprendiz-ano-vai-fechar-com-13-mil-pessoas-qualificadas", 21 | "pubDate": "Seg, 24 Set 2018 19:35:07 -0300", 22 | "content": " Programa Pr� Aprendiz, que � realizado pela Secretaria Municipal de Assist�ncia Social de Apucarana em parceria com o Servi�o Social do Com�rcio (Sesc), ter� continuidade em 2019.�O termo de renova�... ", 23 | "contentSnippet": "Programa Pr� Aprendiz, que � realizado pela Secretaria Municipal de Assist�ncia Social de Apucarana em parceria com o Servi�o Social do Com�rcio (Sesc), ter� continuidade em 2019.�O termo de renova�..." 24 | }, 25 | { 26 | "title": "Dow Jones fecha em baixa de 0,68%", 27 | "link": "https://economia.uol.com.br/noticias/efe/2018/09/24/dow-jones-fecha-em-baixa-de-068.htm", 28 | "pubDate": "Seg, 24 Set 2018 19:32:00 -0300", 29 | "content": " Nova York, 24 set (EFE).- O �ndice Dow Jones Industrial fechou nesta segunda-feira em baixa de 0,68% em mais um preg�o marcado pela disputa comercial entre Estados Unidos e China e por especula��es sobre a poss�vel ren�ncia de Rod Rosenstein ao cargo de procurador-geral adjunto dos Estados Unidos. ", 30 | "contentSnippet": "Nova York, 24 set (EFE).- O �ndice Dow Jones Industrial fechou nesta segunda-feira em baixa de 0,68% em mais um preg�o marcado pela disputa comercial entre Estados Unidos e China e por especula��es sobre a poss�vel ren�ncia de Rod Rosenstein ao cargo de procurador-geral adjunto dos Estados Unidos." 31 | }, 32 | { 33 | "title": "Rival de Putin � condenado a mais 20 dias de pris�o", 34 | "link": "https://noticias.uol.com.br/ultimas-noticias/ansa/2018/09/24/rival-de-putin-e-condenado-a-mais-20-dias-de-prisao.htm", 35 | "pubDate": "Seg, 24 Set 2018 19:32:00 -0300", 36 | "content": " MOSCOU, 24 SET (ANSA) - O l�der de oposi��o russo Alexei Navalny foi condenado na noite desta segunda-feira (24) a mais 20 dias de pris�o por organizar manifesta��es n�o autorizadas contra o governo de Vladimir Putin, informou a m�dia russa. Navalny foi preso nesta manh� pouco tempo depois de ser libertado da cadeia ap�s cumprir outra senten�a de 30 dias.��� ", 37 | "contentSnippet": "MOSCOU, 24 SET (ANSA) - O l�der de oposi��o russo Alexei Navalny foi condenado na noite desta segunda-feira (24) a mais 20 dias de pris�o por organizar manifesta��es n�o autorizadas contra o governo de Vladimir Putin, informou a m�dia russa. Navalny foi preso nesta manh� pouco tempo depois de ser libertado da cadeia ap�s cumprir outra senten�a de 30 dias.���" 38 | }, 39 | { 40 | "title": "Em entrevista, Bolsonaro chama proposta de Paulo Guedes para imposto de renda de \"ousada\"", 41 | "link": "https://noticias.uol.com.br/ultimas-noticias/reuters/2018/09/24/em-entrevista-bolsonaro-chama-proposta-de-paulo-guedes-para-imposto-de-renda-de-ousada.htm", 42 | "pubDate": "Seg, 24 Set 2018 19:30:42 -0300", 43 | "content": " BRAS�LIA (Reuters) - O candidato do PSL � Presid�ncia, Jair Bolsonaro, classificou nesta segunda-feira a proposta do seu principal assessor econ�mico, Paulo Guedes, para a mudan�a na forma de cobran�a do imposto de renda para pessoa f�sica de \"ousada\". ", 44 | "contentSnippet": "BRAS�LIA (Reuters) - O candidato do PSL � Presid�ncia, Jair Bolsonaro, classificou nesta segunda-feira a proposta do seu principal assessor econ�mico, Paulo Guedes, para a mudan�a na forma de cobran�a do imposto de renda para pessoa f�sica de \"ousada\"." 45 | }, 46 | { 47 | "title": "Relat�rio do governo dos EUA acusa militares de Mianmar de atrocidades contra mu�ulmanos rohingyas", 48 | "link": "https://noticias.uol.com.br/ultimas-noticias/reuters/2018/09/24/relatorio-do-governo-dos-eua-acusa-militares-de-mianmar-de-atrocidades-contra-muculmanos-rohingyas.htm", 49 | "pubDate": "Seg, 24 Set 2018 19:29:18 -0300", 50 | "content": " Por Matt Spetalnick e Jason Szep ", 51 | "contentSnippet": "Por Matt Spetalnick e Jason Szep" 52 | }, 53 | { 54 | "title": "Enfermeira de Ch�vez alega 'persegui��o' em Corte espanhola", 55 | "link": "https://noticias.uol.com.br/ultimas-noticias/ansa/2018/09/24/enfermeira-de-chavez-alega-perseguicao-em-corte-espanhola.htm", 56 | "pubDate": "Seg, 24 Set 2018 19:25:00 -0300", 57 | "content": " MADRI, 24 SET (ANSA) - A ex-enfermeira do falecido Hugo Ch�vez, ex-presidente venezuelano, prestou depoimento hoje (24) na Corte Nacional da Espanha e alegou sofrer persegui�... ", 58 | "contentSnippet": "MADRI, 24 SET (ANSA) - A ex-enfermeira do falecido Hugo Ch�vez, ex-presidente venezuelano, prestou depoimento hoje (24) na Corte Nacional da Espanha e alegou sofrer persegui�..." 59 | }, 60 | { 61 | "title": "Match Eleitoral j� soma mais de 500 mil testes completos; ache seu candidato", 62 | "link": "https://www1.folha.uol.com.br/poder/2018/09/match-eleitoral-ja-soma-mais-de-500-mil-testes-completos-ache-seu-candidato.shtml", 63 | "pubDate": "Seg, 24 Set 2018 19:22:00 -0300", 64 | "content": "", 65 | "contentSnippet": "" 66 | }, 67 | { 68 | "title": "Match Eleitoral j� soma mais de 500 mil testes completos; ache seu candidato", 69 | "link": "https://redir.folha.com.br/redir/online/poder/eleicoes-2018/rss091/*https://www1.folha.uol.com.br/poder/2018/09/match-eleitoral-ja-soma-mais-de-500-mil-testes-completos-ache-seu-candidato.shtml", 70 | "pubDate": "Seg, 24 Set 2018 19:22:00 -0300", 71 | "content": " J� s�o mais de 500 mil testes completos no�Match Eleitoral, ferramenta criada pela�Folha�e pelo Datafolha para ajudar o�eleitor a escolher�seu deputado federal por S�o Paulo, Minas Gerais e Rio de Janeiro, al�m de senadores por S�o Paulo.�Leia mais (09/24/2018 - 19h22) ", 72 | "contentSnippet": "J� s�o mais de 500 mil testes completos no�Match Eleitoral, ferramenta criada pela�Folha�e pelo Datafolha para ajudar o�eleitor a escolher�seu deputado federal por S�o Paulo, Minas Gerais e Rio de Janeiro, al�m de senadores por S�o Paulo.�Leia mais (09/24/2018 - 19h22)" 73 | }, 74 | { 75 | "title": "Acordo adicional da Argentina com FMI est� \"perto de acontecer\", diz Macri", 76 | "link": "https://www1.folha.uol.com.br/mercado/2018/09/acordo-adicional-da-argentina-com-fmi-esta-perto-de-acontecer-diz-macri.shtml", 77 | "pubDate": "Seg, 24 Set 2018 19:22:00 -0300", 78 | "content": "", 79 | "contentSnippet": "" 80 | }, 81 | { 82 | "title": "Fernanda Melchionna: a Primavera Feminista vai derrotar Bolsonaro nas ruas e nas urnas!", 83 | "link": "https://agoraequesaoelas.blogfolha.uol.com.br/?p=1609", 84 | "pubDate": "Seg, 24 Set 2018 19:21:00 -0300", 85 | "content": "", 86 | "contentSnippet": "" 87 | }, 88 | { 89 | "title": "Ibope: Bolsonaro estaciona na lideran�a; Haddad segue avan�ando", 90 | "link": "https://congressoemfoco.uol.com.br/eleicoes/ibope-bolsonaro-estaciona-na-lideranca-haddad-segue-avancando/", 91 | "pubDate": "Seg, 24 Set 2018 19:20:08 -0300", 92 | "content": " Em nova pesquisa Ibope divulgada nesta segunda-feira (24), o candidato do PSL � Presid�ncia, Jair Bolsonaro, continua l�der com 28% das inten��es de voto, sem apresentar crescimento em rela��o � �ltima pesquisa, quando teve a mesma pontua��o. O candidato do PT, Fernando Haddad, aproxima-se de Bolsonaro com 22%, uma diferen�a de 3 pontos percentuais em [?] ", 93 | "contentSnippet": "Em nova pesquisa Ibope divulgada nesta segunda-feira (24), o candidato do PSL � Presid�ncia, Jair Bolsonaro, continua l�der com 28% das inten��es de voto, sem apresentar crescimento em rela��o � �ltima pesquisa, quando teve a mesma pontua��o. O candidato do PT, Fernando Haddad, aproxima-se de Bolsonaro com 22%, uma diferen�a de 3 pontos percentuais em [?]" 94 | }, 95 | { 96 | "title": "Assange chegou a renunciar a asilo do Equador, segundo carta privada", 97 | "link": "https://noticias.uol.com.br/ultimas-noticias/afp/2018/09/24/assange-chegou-a-renunciar-a-asilo-do-equador-segundo-carta-privada.htm", 98 | "pubDate": "Seg, 24 Set 2018 19:20:00 -0300", 99 | "content": " Quito, 24 Set 2018 (AFP) - O fundador do WikiLeaks, Julian Assange, refugiado na embaixada equatoriana em Londres h� seis anos, chegou a renunciar ao asilo concedido por Quito, segundo uma carta assinada por ele em dezembro passado � qual a AFP teve acesso. ", 100 | "contentSnippet": "Quito, 24 Set 2018 (AFP) - O fundador do WikiLeaks, Julian Assange, refugiado na embaixada equatoriana em Londres h� seis anos, chegou a renunciar ao asilo concedido por Quito, segundo uma carta assinada por ele em dezembro passado � qual a AFP teve acesso." 101 | }, 102 | { 103 | "title": "Fama \"A\" � campe� da Primeira Divis�o da Copa Cidade Alta de Futebol Su��o", 104 | "link": "https://tnonline.uol.com.br/noticias/apucarana/45,470926,24,09,fama-a-e-campea-da-primeira-divisao-da-copa-cidade-alta-de-futebol-suico", 105 | "pubDate": "Seg, 24 Set 2018 19:18:49 -0300", 106 | "content": " om a Arena Fama lotada, a equipe da Molas Fama/Multividros \"A\" foi campe� neste s�bado da Primeira Divis�o da 10� edi��o da Copa Cidade Alta de Futebol Su��o, categoria livre, ao vencer a Estamparia ... ", 107 | "contentSnippet": "om a Arena Fama lotada, a equipe da Molas Fama/Multividros \"A\" foi campe� neste s�bado da Primeira Divis�o da 10� edi��o da Copa Cidade Alta de Futebol Su��o, categoria livre, ao vencer a Estamparia ..." 108 | } 109 | ], 110 | "image": { 111 | "link": "http://noticias.uol.com.br/ultimas/", 112 | "url": "http://rss.i.uol.com.br/uol_rss.gif", 113 | "title": "UOL Noticias - �ltimas Not�cias" 114 | }, 115 | "title": "UOL Noticias", 116 | "description": "�ltimas Not�cias", 117 | "link": "http://noticias.uol.com.br/", 118 | "language": "", 119 | "copyright": "" 120 | } 121 | } -------------------------------------------------------------------------------- /test/parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var HTTP = require('http'); 5 | 6 | var Parser = require('../index.js'); 7 | 8 | var Expect = require('chai').expect; 9 | 10 | var IN_DIR = __dirname + '/input'; 11 | var OUT_DIR = __dirname + '/output'; 12 | 13 | describe('Parser', function() { 14 | var testParseForFile = function(name, ext, options, done) { 15 | if (typeof done === 'undefined') { 16 | done = options; 17 | options = {}; 18 | } 19 | let parser = new Parser(options); 20 | let xml = fs.readFileSync(IN_DIR + '/' + name + '.' + ext, 'utf8'); 21 | parser.parseString(xml, function(err, parsed) { 22 | if (err) console.log(err); 23 | Expect(err).to.equal(null); 24 | if (process.env.WRITE_GOLDEN) { 25 | fs.writeFileSync(OUT_DIR + '/' + name + '.json', JSON.stringify({feed: parsed}, null, 2)); 26 | } else { 27 | var expected = fs.readFileSync(OUT_DIR + '/' + name + '.json', 'utf8') 28 | expected = JSON.parse(expected); 29 | Expect({feed: parsed}).to.deep.equal(expected); 30 | } 31 | done(); 32 | }) 33 | } 34 | 35 | it('should parse Reddit', function(done) { 36 | testParseForFile('reddit', 'rss', done); 37 | }) 38 | 39 | it('should parse sciencemag.org (RSS 1.0)', function(done) { 40 | testParseForFile('rss-1', 'rss', done); 41 | }) 42 | 43 | it('should parse craigslist (RSS 1.0)', function(done) { 44 | testParseForFile('craigslist', 'rss', done); 45 | }) 46 | 47 | it('should parse atom', function(done) { 48 | testParseForFile('reddit-atom', 'rss', done); 49 | }) 50 | 51 | it('should parse atom feed', function(done) { 52 | testParseForFile('gulp-atom', 'atom', done); 53 | }) 54 | 55 | it('should parse reddits new feed', function(done) { 56 | testParseForFile('reddit-home', 'rss', done); 57 | }) 58 | 59 | it('should parse with missing fields', function(done) { 60 | testParseForFile('missing-fields', 'atom', done) 61 | }) 62 | 63 | it('should parse with incomplete fields', function(done) { 64 | testParseForFile('incomplete-fields', 'atom', done) 65 | }) 66 | 67 | it('should parse heise', function(done) { 68 | testParseForFile('heise', 'atom', done); 69 | }) 70 | 71 | it('should parse heraldsun', function(done) { 72 | testParseForFile('heraldsun', 'rss', done); 73 | }); 74 | 75 | it('should parse UOL Noticias', function(done) { 76 | testParseForFile('uolNoticias', 'rss', { defaultRSS: 2.0 }, done); 77 | }); 78 | 79 | it('should NOT parse UOL Noticias, if no default RSS is provided', function(done) { 80 | function willFail() { 81 | testParseForFile('uolNoticias', 'rss', done); 82 | } 83 | Expect(willFail).to.throw; 84 | done(); 85 | }); 86 | 87 | it('should parse Instant Article', function(done) { 88 | testParseForFile('instant-article', 'rss', done); 89 | }); 90 | 91 | it('should parse Feedburner', function(done) { 92 | testParseForFile('feedburner', 'atom', done); 93 | }); 94 | 95 | it('should parse podcasts', function(done) { 96 | testParseForFile('narro', 'rss', done); 97 | }); 98 | 99 | it('should parse multiple links', function(done) { 100 | testParseForFile('many-links', 'rss', done); 101 | }); 102 | 103 | it('should parse itunes with empty href', function(done) { 104 | testParseForFile('itunes-href', 'rss', done); 105 | }); 106 | 107 | it('should pass xml2js options', function(done) { 108 | testParseForFile('xml2js-options', 'rss', {xml2js: {emptyTag: 'EMPTY'}}, done); 109 | }); 110 | 111 | it('should throw error for unrecognized', function(done) { 112 | let parser = new Parser(); 113 | let xml = fs.readFileSync(__dirname + '/input/unrecognized.rss', 'utf8'); 114 | parser.parseString(xml, function(err, parsed) { 115 | Expect(err.message).to.contain('Feed not recognized as RSS'); 116 | done(); 117 | }); 118 | }); 119 | 120 | it('should omit iTunes image if none available during decoration', function(done) { 121 | const rssFeedWithMissingImage = __dirname + '/input/itunes-missing-image.rss'; 122 | const xml = fs.readFileSync(rssFeedWithMissingImage, 'utf8'); 123 | let parser = new Parser(); 124 | parser.parseString(xml, function(err, parsed) { 125 | Expect(err).to.be.null; 126 | Expect(parsed).to.not.have.deep.property('feed.itunes.image'); 127 | done(); 128 | }); 129 | }); 130 | 131 | it('should parse custom fields', function(done) { 132 | var options = { 133 | customFields: { 134 | feed: ['language', 'copyright', 'nested-field'], 135 | item: ['subtitle'] 136 | } 137 | }; 138 | testParseForFile('customfields', 'rss', options, done); 139 | }); 140 | 141 | it('should parse Atom feed custom fields', function(done) { 142 | var options = { 143 | customFields: { 144 | feed: ['totalViews'], 145 | item: ['media:group'] 146 | } 147 | }; 148 | testParseForFile('atom-customfields', 'atom', options, done); 149 | }); 150 | 151 | it('should parse sibling custom fields', function(done) { 152 | var options = { 153 | customFields: { 154 | item: [['media:content', 'media:content', {keepArray: true}]] 155 | } 156 | }; 157 | testParseForFile('guardian', 'rss', options, done); 158 | }); 159 | 160 | it('should parse URL', function(done) { 161 | var INPUT_FILE = __dirname + '/input/reddit.rss'; 162 | var OUTPUT_FILE = __dirname + '/output/reddit.json'; 163 | var server = HTTP.createServer(function(req, res) { 164 | var file = fs.createReadStream(INPUT_FILE, 'utf8'); 165 | file.pipe(res); 166 | }); 167 | server.listen(function() { 168 | var port = server.address().port; 169 | var url = 'http://localhost:' + port; 170 | let parser = new Parser(); 171 | parser.parseURL(url, function(err, parsed) { 172 | Expect(err).to.equal(null); 173 | if (process.env.WRITE_GOLDEN) { 174 | fs.writeFileSync(OUTPUT_FILE, JSON.stringify({feed: parsed}, null, 2)); 175 | } else { 176 | var expected = JSON.parse(fs.readFileSync(OUTPUT_FILE, 'utf8')); 177 | Expect({feed: parsed}).to.deep.equal(expected); 178 | } 179 | server.close(); 180 | done(); 181 | }); 182 | }); 183 | }); 184 | 185 | it('should parse URL with relative redirect', function(done) { 186 | var INPUT_FILE = __dirname + '/input/reddit.rss'; 187 | var OUTPUT_FILE = __dirname + '/output/reddit.json'; 188 | var server = HTTP.createServer(function(req, res) { 189 | if (req.url !== '/new-location') { 190 | res.writeHead(301, { 'Location': '/new-location'}); 191 | res.end(); 192 | } else { 193 | var file = fs.createReadStream(INPUT_FILE, 'utf8'); 194 | file.pipe(res); 195 | } 196 | }); 197 | server.listen(function() { 198 | var port = server.address().port; 199 | var url = 'http://localhost:' + port; 200 | let parser = new Parser(); 201 | parser.parseURL(url, function(err, parsed) { 202 | Expect(err).to.equal(null); 203 | if (process.env.WRITE_GOLDEN) { 204 | fs.writeFileSync(OUTPUT_FILE, JSON.stringify({feed: parsed}, null, 2)); 205 | } else { 206 | var expected = JSON.parse(fs.readFileSync(OUTPUT_FILE, 'utf8')); 207 | Expect({feed: parsed}).to.deep.equal(expected); 208 | } 209 | server.close(); 210 | done(); 211 | }); 212 | }); 213 | }); 214 | 215 | it('should use proper encoding', function(done) { 216 | var INPUT_FILE = __dirname + '/input/encoding.rss'; 217 | var OUTPUT_FILE = __dirname + '/output/encoding.json'; 218 | var ENCODING = 'latin1'; 219 | var server = HTTP.createServer(function(req, res) { 220 | res.setHeader('Content-Type', 'text/xml; charset=' + ENCODING) 221 | var file = fs.readFileSync(INPUT_FILE, ENCODING); 222 | res.end(file, ENCODING); 223 | }); 224 | server.listen(function() { 225 | var port = server.address().port; 226 | var url = 'http://localhost:' + port; 227 | var parser = new Parser(); 228 | parser.parseURL(url, function(err, parsed) { 229 | Expect(err).to.equal(null); 230 | if (process.env.WRITE_GOLDEN) { 231 | fs.writeFileSync(OUTPUT_FILE, JSON.stringify({feed: parsed}, null, 2), {encoding: ENCODING}); 232 | } else { 233 | var expected = JSON.parse(fs.readFileSync(OUTPUT_FILE, ENCODING)); 234 | Expect({feed: parsed}).to.deep.equal(expected); 235 | } 236 | server.close(); 237 | done(); 238 | }) 239 | }) 240 | }); 241 | 242 | it('should respect timeout option', function(done) { 243 | var server = HTTP.createServer(function(req, res) {}); 244 | server.listen(function() { 245 | var port = server.address().port; 246 | var url = 'http://localhost:' + port; 247 | var parser = new Parser({timeout: 1}); 248 | parser.parseURL(url, function(err, parsed) { 249 | Expect(err).to.not.equal(null); 250 | Expect(err.message).to.equal("Request timed out after 1ms"); 251 | done(); 252 | }); 253 | }); 254 | }); 255 | 256 | it('should parse episodeType', function(done) { 257 | testParseForFile('item-itunes-episodeType', 'rss', done); 258 | }); 259 | 260 | it('should parse itunes categories', function(done) { 261 | testParseForFile('itunes-category', 'rss', done); 262 | }); 263 | 264 | it('should parse itunes keywords', function(done) { 265 | testParseForFile('itunes-keywords', 'rss', done); 266 | }); 267 | 268 | it('should parse itunes keywords as array', function(done) { 269 | testParseForFile('itunes-keywords-array', 'rss', done); 270 | }); 271 | 272 | it('should parse itunes keywords with attribute `text`', function(done) { 273 | testParseForFile('itunes-keywords-astext', 'rss', done); 274 | }); 275 | 276 | it('should parse giantbomb-podcast', function(done) { 277 | testParseForFile('giantbomb-podcast', 'rss', done); 278 | }); 279 | 280 | it('should parse content:encoded', function(done) { 281 | testParseForFile('content-encoded', 'rss', done); 282 | }); 283 | 284 | it('should parse atom:link pagination links', function (done) { 285 | testParseForFile('pagination-links', 'rss', done); 286 | }); 287 | }) 288 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | module.exports = { 3 | entry: { 4 | "rss-parser": "./index.js" 5 | }, 6 | output: { 7 | path: __dirname, 8 | filename: "dist/[name].js", 9 | libraryTarget: 'umd', 10 | globalObject: 'this', 11 | library: 'RSSParser' 12 | }, 13 | resolve: { 14 | extensions: ['.js'] 15 | }, 16 | devtool: 'source-map', 17 | module: { 18 | rules: [{ 19 | test: /\.js$/, 20 | use: [{ 21 | loader: 'babel-loader', 22 | options: {presets: ['@babel/preset-env']}, 23 | }], 24 | }] 25 | }, 26 | externals: { 27 | xmlbuilder:'xmlbuilder' 28 | }, 29 | resolve: { 30 | fallback: { 31 | "https": false, 32 | "http": false, 33 | "url": false, 34 | "stream": false, 35 | "fs": false, 36 | "timers": false, 37 | } 38 | } 39 | } 40 | --------------------------------------------------------------------------------