├── LICENCE ├── Makefile ├── README ├── feedparser-0.71-3.rockspec ├── feedparser.lua ├── feedparser ├── XMLElement.lua ├── dateparser.lua └── url.lua └── tests ├── XMLElement.lua ├── dateparser.lua ├── feedparser.lua ├── feeds ├── atom-ob.xml ├── atom1.0-1.xml ├── rss-reddit.xml └── rss-userland-dawkins.xml └── xml └── simple.xml /LICENCE: -------------------------------------------------------------------------------- 1 | feedparser is available under the (new) BSD license. it uses a 2 | portion of LuaSocket code (copyright 2007 Diego Nehab) 3 | (http://www.keplerproject.org/luaexpat/), which is under the MIT license. 4 | 5 | Copyright (c) 2009 Leo Ponomarev. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * Neither the name of the nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX = /usr/local 2 | # System's lua directory (where Lua libraries are installed) 3 | LUA_DIR= $(PREFIX)/share/lua/5.2 4 | LUA_BIN= lua 5 | 6 | NAME=feedparser 7 | VERSION=0.71 8 | all: 9 | @echo "nothing to make" 10 | @echo "run make install, please. don't forget make test, too" 11 | 12 | test: 13 | ${LUA_BIN} tests/XMLElement.lua 14 | ${LUA_BIN} tests/dateparser.lua 15 | ${LUA_BIN} tests/feedparser.lua 16 | 17 | install: 18 | install feedparser.lua $(LUA_DIR)/ 19 | mkdir -p $(LUA_DIR)/feedparser 20 | install feedparser/* $(LUA_DIR)/feedparser 21 | 22 | bundle: 23 | tar --create --verbose --exclude-vcs --gzip --file=../$(NAME)-$(VERSION).tar.gz ../$(NAME) 24 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | RSS and Atom feed parser, using expat via the luaExpat binding. 2 | Similar to the Universal Feed Parser (http://feedparser.org), 3 | but less good. 4 | 5 | feedparser requires LuaExpat, so you might want to make sure 6 | you've got that installed. 7 | 8 | this feedparser is directly inspired by the Universal Feed Parser 9 | by Mark Pilgrim. the output for a parsed feed is similar enough that 10 | you may consult his site for reference 11 | ( http://feedparser.org/docs/reference.html ) 12 | 13 | Usage: 14 | local feedparser=require("feedparser") 15 | -- note that for Lua < 5.3, the require exports a global "feedparser" 16 | -- In 5.3 and later, it does not. 17 | 18 | local parsed = feedparser.parse(xml_string, optional_base_url) 19 | 20 | --parsed will be a table of elements containing more or less: 21 | { 22 | version=format name and version ("atom10" or "rss20" etc), 23 | format="atom" or "rss", 24 | feed={ --feed information 25 | title="Feed Title", 26 | subtitle="Or: How To Write Haphazard Documentation In 12 Easy Steps" 27 | rights="rights, if any.", --taken from the "rights" or "copyright" tag. 28 | generator="Particularly Clicky Keyboard", --generator, if present. Atom feeds provide this tag, as does rss, under admin:generatorAgent 29 | 30 | author="John Doe", 31 | author_detail={ 32 | name="John Doe", 33 | email="", 34 | }, 35 | link="http://example.org/", 36 | links={ 37 | 1={ 38 | rel="alternate" or "self" or "related" or "via" or "enclosure" or "something else i forgot to mention", 39 | type="content type of the page the feed points to, if present", 40 | href="http://example.org/", --url is resolved. 41 | title="link title, if any" 42 | }, 43 | }, 44 | updated_parsed=1071340202, --parsed date string 45 | updated="2003-12-13T18:30:02Z", --raw date string 46 | 47 | id="urn:uuid:uuid-if-present-nil-otherwise", 48 | contributors={ 49 | --contains a list of contributors, if present. Otherwise, an empty table. 50 | }, 51 | }, 52 | entries={ 53 | 1={ 54 | enclosures={ }, 55 | id="urn:uuid:1225c695-cfb8-4ebb-whatever-if-present", 56 | link="http://example.org/2003/12/13/atom03", 57 | links={ 58 | 1={ 59 | href="http://example.org/2003/12/13/atom03", 60 | }, 61 | }, 62 | updated="2003-12-13T18:30:02Z", 63 | updated_parsed=1071340202, 64 | title="The Exciting conclusion of Atom-Powered Robots Run Amok", 65 | summary="Some text.", 66 | "contributors"={ 67 | --if any. othersie, empty table. 68 | }, 69 | }, 70 | ... 71 | }, 72 | } 73 | 74 | 75 | The following will NOT appear in the parsed feed table: 76 | 77 | feed.info 78 | feed.info_detail 79 | feed.title_detail 80 | feed.subtitle_detail 81 | feed.rights_detail 82 | feed.textinput 83 | feed.cloud 84 | feed.publisher 85 | feed.publisher_detail 86 | feed.tags 87 | feed.ttl 88 | feed.licence 89 | feed.errorreportsto 90 | 91 | entries[i].title_detail 92 | entries[i].publisher 93 | entries[i].source 94 | entries[i].comments 95 | entries[i].licence 96 | 97 | namespaces 98 | encoding --this is pretty important, so will be implemented sooner than anything else, if anyone asks for it. 99 | status 100 | gref 101 | etag 102 | modified 103 | headers 104 | bozo 105 | bozo_exception 106 | -------------------------------------------------------------------------------- /feedparser-0.71-3.rockspec: -------------------------------------------------------------------------------- 1 | #!/bin/lua 2 | package = "feedparser" 3 | version = "0.71-3" 4 | source = { 5 | url="git://github.com/slact/lua-feedparser", 6 | tag="0.71" 7 | } 8 | description = { 9 | summary = " A decent RSS and Atom XML feed parser", 10 | detailed = [[ 11 | RSS and Atom feed parser, using expat via the luaExpat binding. 12 | Similar to the Universal Feed Parser (http://feedparser.org), 13 | but less good.]], 14 | homepage = "https://github.com/slact/lua-feedparser", 15 | license = "BSD" 16 | } 17 | dependencies = { 18 | "lua >= 5.1, < 5.4", 19 | "luaexpat" 20 | } 21 | build = { 22 | type = "builtin", 23 | modules = { 24 | feedparser = "feedparser.lua", 25 | ['feedparser.XMLElement'] = "feedparser/XMLElement.lua", 26 | ['feedparser.dateparser'] = "feedparser/dateparser.lua", 27 | ['feedparser.url'] = "feedparser/url.lua" 28 | }, 29 | copy_directories = { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /feedparser.lua: -------------------------------------------------------------------------------- 1 | local LOM = assert(require("lxp.lom"), "LuaExpat doesn't seem to be installed. feedparser kind of needs it to work...") 2 | local XMLElement = require "feedparser.XMLElement" 3 | local dateparser = require "feedparser.dateparser" 4 | local URL = require "feedparser.url" 5 | local tinsert, tremove, tconcat = table.insert, table.remove, table.concat 6 | local pairs, ipairs = pairs, ipairs 7 | 8 | --- feedparser, similar to the Universal Feed Parser for python, but a good deal weaker. 9 | -- see http://feedparser.org for details about the Universal Feed Parser 10 | local feedparser= { 11 | _DESCRIPTION = "RSS and Atom feed parser", 12 | _VERSION = "feedparser 0.71" 13 | } 14 | 15 | local blanky = XMLElement.new() --useful in a whole bunch of places 16 | 17 | local function resolve(url, base_url) 18 | return URL.absolute(base_url, url) 19 | end 20 | 21 | local function rebase(el, base_uri) 22 | local xml_base = el:getAttr('xml:base') 23 | if not xml_base then return base_uri end 24 | return resolve(xml_base, base_uri) 25 | end 26 | 27 | local function parse_entries(entries_el, format_str, base) 28 | local entries = {} 29 | for i, entry_el in ipairs(entries_el) do 30 | local entry = {enclosures={}, links={}, contributors={}} 31 | local entry_base = rebase(entry_el, base) 32 | for i, el in ipairs(entry_el:getChildren('*')) do 33 | local tag = el:getTag() 34 | local el_base = rebase(el, entry_base) 35 | --title 36 | if tag == 'title' or tag == 'dc:title' or tag =='rdf:title' then --'dc:title' doesn't occur in atom feeds, but whatever. 37 | entry.title=el:getText() 38 | 39 | --link(s) 40 | elseif format_str == 'rss' and tag=='link' then 41 | entry.link=resolve(el:getText(), el_base) 42 | tinsert(entry.links, {href=entry.link}) 43 | 44 | elseif (format_str=='atom' and tag == 'link') or 45 | (format_str == 'rss' and tag=='atom:link') then 46 | local link = {} 47 | for i, attr in ipairs{'rel','type', 'href','title'} do 48 | link[attr]= (attr=='href') and resolve(el:getAttr(attr), el_base) or el:getAttr(attr) --uri 49 | end 50 | tinsert(entry.links, link) 51 | if link.rel=='enclosure' then 52 | tinsert(entry.enclosures, { 53 | href=link.href, 54 | length=el:getAttr('length'), 55 | type=el:getAttr('type') 56 | }) 57 | end 58 | 59 | --rss enclosures 60 | elseif format_str == 'rss' and tag=='enclosure' then 61 | tinsert(entry.enclosures, { 62 | url=el:getAttr('url'), 63 | length=el:getAttr('length'), 64 | type=el:getAttr('type') 65 | }) 66 | 67 | --summary 68 | elseif (format_str=='atom' and tag=='summary') or 69 | (format_str=='rss' and(tag=='description' or tag=='dc:description' or tag=='rdf:description')) then 70 | entry.summary=el:getText() 71 | --TODO: summary_detail 72 | 73 | --content 74 | elseif (format_str=='atom' and tag=='content') or 75 | (format_str=='rss' and (tag=='body' or tag=='xhtml:body' or tag == 'fullitem' or tag=='content:encoded')) then 76 | entry.content=el:getText() 77 | --TODO: content_detail 78 | 79 | --published 80 | elseif (format_str == 'atom' and (tag=='published' or tag=='issued')) or 81 | (format_str == 'rss' and (tag=='dcterms:issued' or tag=='atom:published' or tag=='atom:issued')) then 82 | entry.published = el:getText() 83 | entry.published_parsed=dateparser.parse(entry.published) 84 | 85 | --updated 86 | elseif (format_str=='atom' and (tag=='updated' or tag=='modified')) or 87 | (format_str=='rss' and (tag=='dc:date' or tag=='pubDate' or tag=='dcterms:modified')) then 88 | entry.updated=el:getText() 89 | entry.updated_parsed=dateparser.parse(entry.updated) 90 | 91 | elseif tag=='created' or tag=='atom:created' or tag=='dcterms:created' then 92 | entry.created=el:getText() 93 | entry.created_parsed=dateparser.parse(entry.created) 94 | 95 | --id 96 | elseif (format_str =='atom' and tag=='id') or 97 | (format_str=='rss' and tag=='guid') then 98 | entry.id=resolve(el:getText(), el_base) -- this is a uri, right?... 99 | 100 | --author 101 | elseif format_str=='rss' and (tag=='author' or tag=='dc:creator') then --author tag should give the author's email. should I respect this? 102 | entry.author=(el:getChild('name') or el):getText() 103 | entry.author_detail={ 104 | name=entry.author 105 | } 106 | elseif format_str=='atom' and tag=='author' then 107 | entry.author=(el:getChild('name') or el):getText() 108 | entry.author_detail = { 109 | name=entry.author, 110 | email=(el:getChild('email') or blanky):getText() 111 | } 112 | local author_url = (el:getChild('url') or blanky):getText() 113 | if author_url and author_url ~= "" then entry.author_detail.href=resolve(author_url, rebase(el:getChild('url'), el_base)) end 114 | 115 | elseif tag=='category' or tag=='dc:subject' then 116 | --todo 117 | 118 | elseif tag=='source' then 119 | --todo 120 | end 121 | end 122 | 123 | --wrap up rss guid 124 | if format_str == 'rss' and (not entry.id) and entry_el:getAttr('rdf:about') then 125 | entry.id=resolve(entry_el:getAttr('rdf:about'), entry_base) --uri 126 | end 127 | 128 | --wrap up entry.link 129 | for i, link in pairs(entry.links) do 130 | if link.rel=="alternate" or (not link.rel) or link.rel=="" then 131 | entry.link=link.href --already resolved. 132 | break 133 | end 134 | end 135 | if not entry.link and format_str=='rss' then 136 | entry.link=entry.id 137 | end 138 | tinsert(entries, entry) 139 | end 140 | return entries 141 | end 142 | 143 | local function atom_person_construct(person_el, base_uri) 144 | local dude ={ 145 | name= (person_el:getChild('name') or blanky):getText(), 146 | email=(person_el:getChild('email') or blanky):getText() 147 | } 148 | local url_el = person_el:getChild('url') 149 | if url_el then dude.href=resolve(url_el:getText(), rebase(url_el, base_uri)) end 150 | return dude 151 | end 152 | 153 | local function parse_atom(root, base_uri) 154 | local res = {} 155 | local feed = { 156 | links = {}, 157 | contributors={}, 158 | language = root:getAttr('lang') or root:getAttr('xml:lang') 159 | } 160 | local root_base = rebase(root, base_uri) 161 | res.feed=feed 162 | res.format='atom' 163 | local version=(root:getAttr('version') or ''):lower() 164 | if version=="1.0" or root:getAttr('xmlns')=='http://www.w3.org/2005/Atom' then res.version='atom10' 165 | elseif version=="0.3" then res.version='atom03' 166 | else res.version='atom' end 167 | 168 | for i, el in ipairs(root:getChildren('*')) do 169 | local tag = el:getTag() 170 | local el_base=rebase(el, root_base) 171 | if tag == 'title' or tag == 'dc:title' or tag == 'atom10:title' or tag == 'atom03:title' then 172 | feed.title=el:getText() --sanitize! 173 | --todo: feed.title_detail 174 | 175 | --link stuff 176 | elseif tag=='link' then 177 | local link = {} 178 | for i, attr in ipairs{'rel','type', 'href','title'} do 179 | link[attr]= (attr=='href') and resolve(el:getAttr(attr), el_base) or el:getAttr(attr) 180 | end 181 | tinsert(feed.links, link) 182 | 183 | --subtitle 184 | elseif tag == 'subtitle' then 185 | feed.subtitle=el:getText() --sanitize! 186 | elseif not feed.subtitle and (tag == 'tagline' or tag =='atom03:tagline' or tag=='dc:description') then 187 | feed.subtitle=el:getText() --sanitize! 188 | 189 | --rights 190 | elseif tag == 'copyright' or tag == 'rights' then 191 | feed.rights=el:getText() --sanitize! 192 | 193 | --generator 194 | elseif tag == 'generator' then 195 | feed.generator=el:getText() --sanitize! 196 | elseif tag == 'admin:generatorAgent' then 197 | feed.generator = feed.generator or el:getAttr('rdf:resource') 198 | 199 | --info 200 | elseif tag == 'info' then --whatever, nobody cared, anyway. 201 | feed.info = el:getText() 202 | 203 | --id 204 | elseif tag=='id' then 205 | feed.id=resolve(el:getText(), el_base) --this is a url, right?.,, 206 | 207 | --updated 208 | elseif tag == 'updated' or tag == 'dc:date' or tag == 'modified' or tag=='rss:pubDate' then 209 | feed.updated = el:getText() 210 | feed.updated_parsed=dateparser.parse(feed.updated) 211 | 212 | --author 213 | elseif tag=='author' or tag=='atom:author' then 214 | feed.author_detail=atom_person_construct(el, el_base) 215 | feed.author=feed.author_detail.name 216 | 217 | --contributors 218 | elseif tag=='contributor' or tag=='atom:contributor' then 219 | tinsert(feed.contributors, atom_person_construct(el, el_base)) 220 | 221 | --icon 222 | elseif tag=='icon' then 223 | feed.icon=resolve(el:getText(), el_base) 224 | 225 | --logo 226 | elseif tag=='logo' then 227 | feed.logo=resolve(el:getText(), el_base) 228 | 229 | --language 230 | elseif tag=='language' or tag=='dc:language' then 231 | feed.language=feed.language or el:getText() 232 | 233 | --licence 234 | end 235 | end 236 | --feed.link (already resolved) 237 | for i, link in pairs(feed.links) do 238 | if link.rel=='alternate' or not link.rel or link.rel=='' then 239 | feed.link=link.href 240 | break 241 | end 242 | end 243 | 244 | res.entries=parse_entries(root:getChildren('entry'),'atom', root_base) 245 | return res 246 | end 247 | 248 | local function parse_rss(root, base_uri) 249 | 250 | local channel = root:getChild({'channel', 'rdf:channel'}) 251 | local channel_base = rebase(channel, base_uri) 252 | if not channel then return nil, "can't parse that." end 253 | 254 | local feed = {links = {}, contributors={}} 255 | local res = { 256 | feed=feed, 257 | format='rss', 258 | entries={} 259 | } 260 | 261 | --this isn't quite right at all. 262 | if root:getTag():lower()=='rdf:rdf' then 263 | res.version='rss10' 264 | else 265 | res.version='rss20' 266 | end 267 | 268 | for i, el in ipairs(channel:getChildren('*')) do 269 | local el_base=rebase(el, channel_base) 270 | local tag = el:getTag() 271 | 272 | if tag=='link' then 273 | feed.link=resolve(el:getText(), el_base) 274 | tinsert(feed.links, {href=feed.link}) 275 | 276 | --title 277 | elseif tag == 'title' or tag == 'dc:title' then 278 | feed.title=el:getText() --sanitize! 279 | 280 | --subtitle 281 | elseif tag == 'description' or tag =='dc:description' or tag=='itunes:subtitle' then 282 | feed.subtitle=el:getText() --sanitize! 283 | 284 | --rights 285 | elseif tag == 'copyright' or tag == 'dc:rights' then 286 | feed.rights=el:getText() --sanitize! 287 | 288 | --generator 289 | elseif tag == 'generator' then 290 | feed.generator=el:getText() 291 | elseif tag == 'admin:generatorAgent' then 292 | feed.generator = feed.generator or el:getAttr('rdf:resource') 293 | 294 | --info (nobody cares...) 295 | elseif tag == 'feedburner:browserFriendly' then 296 | feed.info = el:getText() 297 | 298 | --updated 299 | elseif tag == 'pubDate' or tag == 'dc:date' or tag == 'dcterms:modified' then 300 | feed.updated = el:getText() 301 | feed.updated_parsed = dateparser.parse(feed.updated) 302 | 303 | --author 304 | elseif tag=='managingEditor' or tag =='dc:creator' or tag=='itunes:author' or tag =='dc:creator' or tag=='dc:author' then 305 | feed.author=tconcat(el:getChildren('text()')) 306 | feed.author_details={name=feed.author} 307 | elseif tag=='atom:author' then 308 | feed.author_details = atom_person_construct(el, el_base) 309 | feed.author = feed.author_details.name 310 | 311 | --contributors 312 | elseif tag == 'dc:contributor' then 313 | tinsert(feed.contributors, {name=el:getText()}) 314 | elseif tag == 'atom:contributor' then 315 | tinsert(feed.contributors, atom_person_construct(el, el_base)) 316 | 317 | --image 318 | elseif tag=='image' or tag=='rdf:image' then 319 | feed.image={ 320 | title=el:getChild('title'):getText(), 321 | link=(el:getChild('link') or blanky):getText(), 322 | width=(el:getChild('width') or blanky):getText(), 323 | height=(el:getChild('height') or blanky):getText() 324 | } 325 | local url_el = el:getChild('url') 326 | if url_el then feed.image.href = resolve(url_el:getText(), rebase(url_el, el_base)) end 327 | 328 | --language 329 | elseif tag=='language' or tag=='dc:language' then 330 | feed.language=el:getText() 331 | 332 | --licence 333 | --publisher 334 | --tags 335 | end 336 | end 337 | 338 | res.entries=parse_entries(channel:getChildren('item'),'rss', channel_base) 339 | return res 340 | end 341 | 342 | 343 | --- parse feed xml 344 | -- @param xml_string feed xml, as a string 345 | -- @param base_url (optional) source url of the feed. useful when resolving relative links found in feed contents 346 | -- @return table with parsed feed info, or nil, error_message on error. 347 | -- the format of the returned table is much like that on http://feedparser.org, with the major difference that 348 | -- dates are parsed into unixtime. Most other fields are very much the same. 349 | function feedparser.parse(xml_string, base_url) 350 | local lom, err = LOM.parse(xml_string) 351 | if not lom then return nil, "couldn't parse xml. lxp says: " .. err or "nothing" end 352 | local rootElement = XMLElement.new(lom) 353 | local root_tag = rootElement:getTag():lower() 354 | if root_tag=='rdf:rdf' or root_tag=='rss' then 355 | return parse_rss(rootElement, base_url) 356 | elseif root_tag=='feed' then 357 | return parse_atom(rootElement, base_url) 358 | else 359 | return nil, "unknown feed format" 360 | end 361 | end 362 | 363 | --for the sake of backwards-compatibility, feedparser will export a global reference for lua < 5.3 364 | if _VERSION:sub(-3) < "5.3" then 365 | _G.feedparser=feedparser 366 | end 367 | 368 | 369 | return feedparser -------------------------------------------------------------------------------- /feedparser/XMLElement.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2009 Leo Ponomarev. Distributed under the BSD Licence. 2 | -- updated for module-free world of lua 5.3 on April 2 2015 3 | -- Not documented at all, but not interesting enough to warrant documentation anyway. 4 | local setmetatable, pairs, ipairs, type, getmetatable, tostring, error = setmetatable, pairs, ipairs, type, getmetatable, tostring, error 5 | local table, string = table, string 6 | 7 | local XMLElement={} 8 | 9 | local mt 10 | 11 | XMLElement.new = function(lom) 12 | return setmetatable({lom=lom or {}}, mt) 13 | end 14 | 15 | local function filter(filtery_thing, lom) 16 | filtery_thing=filtery_thing or '*' 17 | for i, thing in ipairs(type(filtery_thing)=='table' and filtery_thing or {filtery_thing}) do 18 | if thing == "text()" then 19 | if type(lom)=='string' then return true end 20 | elseif thing == '*' then 21 | if type(lom)=='table' then return true end 22 | else 23 | if type(lom)=='table' and string.lower(lom.tag)==string.lower(thing) then return true end 24 | end 25 | end 26 | return nil 27 | end 28 | 29 | 30 | mt ={ __index = { 31 | getAttr = function(self, attribute) 32 | if type(attribute) ~= "string" then return nil, "attribute name must be a string." end 33 | return self.lom.attr[attribute] 34 | end, 35 | 36 | setAttr = function(self, attribute, value) 37 | if type(attribute) ~= "string" then return nil, "attribute name must be a string." end 38 | if value == nil then return self:removeAttr(attribute) end 39 | self.lom.attr[attribute]=tostring(value) 40 | return self 41 | end, 42 | 43 | removeAttr = function(self, attribute) 44 | local lom = self.lom 45 | if type(attribute) ~= "string" then return nil, "attribute name must be a string." end 46 | if not lom.attr[attribute] then return self end 47 | for i,v in ipairs(lom.attr) do 48 | if v == attribute then 49 | table.remove(lom.attr, i) 50 | break 51 | end 52 | end 53 | lom.attr[attribute]=nil 54 | end, 55 | 56 | removeAllAttributes = function(self) 57 | local attr = self.lom.attr 58 | for i, v in pairs(self.lom.attr) do 59 | attr[i]=nil 60 | end 61 | return self 62 | end, 63 | 64 | getAttributes = function(self) 65 | local attr = {} 66 | for i, v in ipairs(self.lom.attr) do 67 | table.insert(attr,v) 68 | end 69 | return attr 70 | end, 71 | 72 | getXML = function(self) 73 | local function getXML(lom) 74 | local attr, inner = {}, {} 75 | for i, attribute in ipairs(lom.attr) do 76 | table.insert(attr, string.format('%s=%q', attribute, lom.attr[attribute])) 77 | end 78 | for i, v in ipairs(lom) do 79 | local t = type(v) 80 | if t == "string" then table.insert(inner, v) 81 | elseif t == "table" then 82 | table.insert(inner, getXML(v)) 83 | else 84 | error("oh shit") 85 | end 86 | end 87 | local tagcontents = table.concat(inner) 88 | local attrstring = #attr>0 and (" " .. table.concat(attr, " ")) or "" 89 | if #tagcontents>0 then 90 | return string.format("<%s%s>%s", lom.tag, attrstring, tagcontents, lom.tag) 91 | else 92 | return string.format("<%s%s />", lom.tag, attrstring) 93 | end 94 | end 95 | return getXML(self.lom) 96 | end, 97 | 98 | getText = function(self) 99 | local function getText(lom) 100 | local inner = {} 101 | for i, v in ipairs(lom) do 102 | local t = type(v) 103 | if t == "string" then table.insert(inner, v) 104 | elseif t == "table" then 105 | table.insert(inner, getText(v)) 106 | end 107 | end 108 | return table.concat(inner) 109 | end 110 | return getText(self.lom) 111 | end, 112 | 113 | getChildren = function(self, filter_thing) 114 | local res = {} 115 | for i, node in ipairs(self.lom) do 116 | if filter(filter_thing, node) then 117 | table.insert(res, type(node)=='table' and XMLElement.new(node) or node) 118 | end 119 | end 120 | return res 121 | end, 122 | 123 | getDescendants = function(self, filter_thing) 124 | local res = {} 125 | local function descendants(lom) 126 | for i, child in ipairs(lom) do 127 | if filter(filter_thing, child) then 128 | table.insert(res, type(child)=='table' and XMLElement.new(child) or child) 129 | if type(child)=='table' then descendants(child) end 130 | end 131 | end 132 | end 133 | descendants(self.lom) 134 | return res 135 | end, 136 | 137 | 138 | getChild = function(self, filter_thing) 139 | for i, node in ipairs(self.lom) do 140 | if filter(filter_thing, node) then 141 | return type(node)=='table' and XMLElement.new(node) or node 142 | end 143 | end 144 | end, 145 | 146 | getTag = function(self) 147 | return self.lom.tag 148 | end 149 | }} 150 | 151 | return XMLElement -------------------------------------------------------------------------------- /feedparser/dateparser.lua: -------------------------------------------------------------------------------- 1 | local difftime, time, date = os.difftime, os.time, os.date 2 | local format = string.format 3 | local tremove, tinsert = table.remove, table.insert 4 | local pcall, pairs, ipairs, tostring, tonumber, type, setmetatable = pcall, pairs, ipairs, tostring, tonumber, type, setmetatable 5 | 6 | local dateparser={} 7 | 8 | --we shall use the host OS's time conversion facilities. Dealing with all those leap seconds by hand can be such a bore. 9 | local unix_timestamp 10 | do 11 | local now = time() 12 | local local_UTC_offset_sec = difftime(time(date("!*t", now)), time(date("*t", now))) 13 | unix_timestamp = function(t, offset_sec) 14 | local success, improper_time = pcall(time, t) 15 | if not success or not improper_time then return nil, "invalid date. os.time says: " .. (improper_time or "nothing") end 16 | return improper_time - local_UTC_offset_sec - offset_sec 17 | end 18 | end 19 | 20 | local formats = {} -- format names 21 | local format_func = setmetatable({}, {__mode='v'}) --format functions 22 | 23 | ---register a date format parsing function 24 | function dateparser.register_format(format_name, format_function) 25 | if type(format_name)~="string" or type(format_function)~='function' then return nil, "improper arguments, can't register format handler" end 26 | 27 | local found 28 | for i, f in ipairs(format_func) do --for ordering 29 | if f==format_function then 30 | found=true 31 | break 32 | end 33 | end 34 | if not found then 35 | tinsert(format_func, format_function) 36 | end 37 | formats[format_name] = format_function 38 | return true 39 | end 40 | 41 | ---register a date format parsing function 42 | function dateparser.unregister_format(format_name) 43 | if type(format_name)~="string" then return nil, "format name must be a string" end 44 | formats[format_name]=nil 45 | end 46 | 47 | ---return the function responsible for handling format_name date strings 48 | function dateparser.get_format_function(format_name) 49 | return formats[format_name] or nil, ("format %s not registered"):format(format_name) 50 | end 51 | 52 | ---try to parse date string 53 | --@param str date string 54 | --@param date_format optional date format name, if known 55 | --@return unix timestamp if str can be parsed; nil, error otherwise. 56 | function dateparser.parse(str, date_format) 57 | local success, res, err 58 | if date_format then 59 | if not formats[date_format] then return 'unknown date format: ' .. tostring(date_format) end 60 | success, res = pcall(formats[date_format], str) 61 | else 62 | for i, func in ipairs(format_func) do 63 | success, res = pcall(func, str) 64 | if success and res then return res end 65 | end 66 | end 67 | return success and res 68 | end 69 | 70 | dateparser.register_format('W3CDTF', function(rest) 71 | 72 | local year, day_of_year, month, day, week 73 | local hour, minute, second, second_fraction, offset_hours 74 | 75 | local alt_rest 76 | 77 | year, rest = rest:match("^(%d%d%d%d)%-?(.*)$") 78 | 79 | day_of_year, alt_rest = rest:match("^(%d%d%d)%-?(.*)$") 80 | 81 | if day_of_year then rest=alt_rest end 82 | 83 | month, rest = rest:match("^(%d%d)%-?(.*)$") 84 | 85 | day, rest = rest:match("^(%d%d)(.*)$") 86 | if #rest>0 then 87 | rest = rest:match("^T(.*)$") 88 | hour, rest = rest:match("^([0-2][0-9]):?(.*)$") 89 | minute, rest = rest:match("^([0-6][0-9]):?(.*)$") 90 | second, rest = rest:match("^([0-6][0-9])(.*)$") 91 | second_fraction, alt_rest = rest:match("^%.(%d+)(.*)$") 92 | if second_fraction then 93 | rest=alt_rest 94 | end 95 | if rest=="Z" then 96 | rest="" 97 | offset_hours=0 98 | else 99 | local sign, offset_h, offset_m 100 | sign, offset_h, rest = rest:match("^([+-])(%d%d)%:?(.*)$") 101 | local offset_m, alt_rest = rest:match("^(%d%d)(.*)$") 102 | if offset_m then rest=alt_rest end 103 | offset_hours = tonumber(sign .. offset_h) + (tonumber(offset_m) or 0)/60 104 | end 105 | if #rest>0 then return nil end 106 | end 107 | 108 | year = tonumber(year) 109 | local d = { 110 | year = year and (year > 100 and year or (year < 50 and (year + 2000) or (year + 1900))), 111 | month = tonumber(month) or 1, 112 | day = tonumber(day) or 1, 113 | hour = tonumber(hour) or 0, 114 | min = tonumber(minute) or 0, 115 | sec = tonumber(second) or 0, 116 | isdst = false 117 | } 118 | local t = unix_timestamp(d, (offset_hours or 0) * 3600) 119 | if second_fraction then 120 | return t + tonumber("0."..second_fraction) 121 | else 122 | return t 123 | end 124 | end) 125 | 126 | 127 | do 128 | local tz_table = { --taken from http://www.timeanddate.com/library/abbreviations/timezones/ 129 | A = 1, B = 2, C = 3, D = 4, E=5, F = 6, G = 7, H = 8, I = 9, 130 | K = 10, L = 11, M = 12, N = -1, O = -2, P = -3, Q = -4, R = -5, 131 | S = -6, T = -7, U = -8, V = -9, W = -10, X = -11, Y = -12, 132 | Z = 0, 133 | 134 | EST = -5, EDT = -4, CST = -6, CDT = -5, 135 | MST = -7, MDT = -6, PST = -8, PDT = -7, 136 | 137 | GMT = 0, UT = 0, UTC = 0 138 | } 139 | 140 | local month_val = {Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12} 141 | 142 | dateparser.register_format('RFC2822', function(rest) 143 | 144 | local year, month, day, day_of_year, week_of_year, weekday 145 | local hour, minute, second, second_fraction, offset_hours 146 | 147 | local alt_rest 148 | 149 | weekday, alt_rest = rest:match("^(%w%w%w),%s+(.*)$") 150 | if weekday then rest=alt_rest end 151 | day, rest=rest:match("^(%d%d?)%s+(.*)$") 152 | month, rest=rest:match("^(%w%w%w)%s+(.*)$") 153 | month = month_val[month] 154 | year, rest = rest:match("^(%d%d%d?%d?)%s+(.*)$") 155 | hour, rest = rest:match("^(%d%d?):(.*)$") 156 | minute, rest = rest:match("^(%d%d?)(.*)$") 157 | second, alt_rest = rest:match("^:(%d%d)(.*)$") 158 | if second then rest = alt_rest end 159 | local tz, offset_sign, offset_h, offset_m 160 | tz, alt_rest = rest:match("^%s+(%u+)(.*)$") 161 | if tz then 162 | rest = alt_rest 163 | offset_hours = tz_table[tz] 164 | else 165 | offset_sign, offset_h, offset_m, rest = rest:match("^%s+([+-])(%d%d)(%d%d)%s*(.*)$") 166 | offset_hours = tonumber(offset_sign .. offset_h) + (tonumber(offset_m) or 0)/60 167 | end 168 | 169 | if #rest>0 or not (year and day and month and hour and minute) then 170 | return nil 171 | end 172 | 173 | year = tonumber(year) 174 | local d = { 175 | year = year and ((year > 100) and year or (year < 50 and (year + 2000) or (year + 1900))), 176 | month = month, 177 | day = tonumber(day), 178 | 179 | hour= tonumber(hour) or 0, 180 | min = tonumber(minute) or 0, 181 | sec = tonumber(second) or 0, 182 | isdst = false 183 | } 184 | return unix_timestamp(d, offset_hours * 3600) 185 | end) 186 | end 187 | 188 | dateparser.register_format('RFC822', formats.RFC2822) --2822 supercedes 822, but is not a strict superset. For our intents and purposes though, it's perfectly good enough 189 | dateparser.register_format('RFC3339', formats.W3CDTF) --RFC3339 is a subset of W3CDTF 190 | 191 | 192 | return dateparser -------------------------------------------------------------------------------- /feedparser/url.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright 2004-2007 Diego Nehab. 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | ]] 24 | ----------------------------------------------------------------------------- 25 | -- URI parsing, composition and relative URL resolution 26 | -- LuaSocket toolkit. 27 | -- Author: Diego Nehab 28 | -- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ 29 | ----------------------------------------------------------------------------- 30 | 31 | 32 | -- updated for a module()-free world of 5.3 by slact 33 | 34 | 35 | local string = require("string") 36 | local base = _G 37 | local table = require("table") 38 | 39 | local Url={} 40 | Url._VERSION = "URL 1.0.2" 41 | 42 | function Url.escape(s) 43 | return string.gsub(s, "([^A-Za-z0-9_])", function(c) 44 | return string.format("%%%02x", string.byte(c)) 45 | end) 46 | end 47 | 48 | local function make_set(t) 49 | local s = {} 50 | for i,v in base.ipairs(t) do 51 | s[t[i]] = 1 52 | end 53 | return s 54 | end 55 | 56 | local segment_set = make_set { 57 | "-", "_", ".", "!", "~", "*", "'", "(", 58 | ")", ":", "@", "&", "=", "+", "$", ",", 59 | } 60 | 61 | local function protect_segment(s) 62 | return string.gsub(s, "([^A-Za-z0-9_])", function (c) 63 | if segment_set[c] then return c 64 | else return string.format("%%%02x", string.byte(c)) end 65 | end) 66 | end 67 | 68 | function Url.unescape(s) 69 | return string.gsub(s, "%%(%x%x)", function(hex) 70 | return string.char(base.tonumber(hex, 16)) 71 | end) 72 | end 73 | 74 | local function absolute_path(base_path, relative_path) 75 | if string.sub(relative_path, 1, 1) == "/" then return relative_path end 76 | local path = string.gsub(base_path, "[^/]*$", "") 77 | path = path .. relative_path 78 | path = string.gsub(path, "([^/]*%./)", function (s) 79 | if s ~= "./" then return s else return "" end 80 | end) 81 | path = string.gsub(path, "/%.$", "/") 82 | local reduced 83 | while reduced ~= path do 84 | reduced = path 85 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) 86 | if s ~= "../../" then return "" else return s end 87 | end) 88 | end 89 | path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) 90 | if s ~= "../.." then return "" else return s end 91 | end) 92 | return path 93 | end 94 | 95 | 96 | ----------------------------------------------------------------------------- 97 | -- Parses a url and returns a table with all its parts according to RFC 2396 98 | -- The following grammar describes the names given to the URL parts 99 | -- ::= :///;?# 100 | -- ::= @: 101 | -- ::= [:] 102 | -- :: = {/} 103 | -- Input 104 | -- url: uniform resource locator of request 105 | -- default: table with default values for each field 106 | -- Returns 107 | -- table with the following fields, where RFC naming conventions have 108 | -- been preserved: 109 | -- scheme, authority, userinfo, user, password, host, port, 110 | -- path, params, query, fragment 111 | -- Obs: 112 | -- the leading '/' in {/} is considered part of 113 | ----------------------------------------------------------------------------- 114 | function Url.parse(url, default) 115 | -- initialize default parameters 116 | local parsed = {} 117 | for i,v in base.pairs(default or parsed) do parsed[i] = v end 118 | -- empty url is parsed to nil 119 | if not url or url == "" then return nil, "invalid url" end 120 | -- remove whitespace 121 | -- url = string.gsub(url, "%s", "") 122 | -- get fragment 123 | url = string.gsub(url, "#(.*)$", function(f) 124 | parsed.fragment = f 125 | return "" 126 | end) 127 | -- get scheme 128 | url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", 129 | function(s) parsed.scheme = s; return "" end) 130 | -- get authority 131 | url = string.gsub(url, "^//([^/]*)", function(n) 132 | parsed.authority = n 133 | return "" 134 | end) 135 | -- get query stringing 136 | url = string.gsub(url, "%?(.*)", function(q) 137 | parsed.query = q 138 | return "" 139 | end) 140 | -- get params 141 | url = string.gsub(url, "%;(.*)", function(p) 142 | parsed.params = p 143 | return "" 144 | end) 145 | -- path is whatever was left 146 | if url ~= "" then parsed.path = url end 147 | local authority = parsed.authority 148 | if not authority then return parsed end 149 | authority = string.gsub(authority,"^([^@]*)@", 150 | function(u) parsed.userinfo = u; return "" end) 151 | authority = string.gsub(authority, ":([^:]*)$", 152 | function(p) parsed.port = p; return "" end) 153 | if authority ~= "" then parsed.host = authority end 154 | local userinfo = parsed.userinfo 155 | if not userinfo then return parsed end 156 | userinfo = string.gsub(userinfo, ":([^:]*)$", 157 | function(p) parsed.password = p; return "" end) 158 | parsed.user = userinfo 159 | return parsed 160 | end 161 | 162 | ----------------------------------------------------------------------------- 163 | -- Rebuilds a parsed URL from its components. 164 | -- Components are protected if any reserved or unallowed characters are found 165 | -- Input 166 | -- parsed: parsed URL, as returned by parse 167 | -- Returns 168 | -- a stringing with the corresponding URL 169 | ----------------------------------------------------------------------------- 170 | function Url.build(parsed) 171 | local ppath = Url.parse_path(parsed.path or "") 172 | local url = Url.build_path(ppath) 173 | if parsed.params then url = url .. ";" .. parsed.params end 174 | if parsed.query then url = url .. "?" .. parsed.query end 175 | local authority = parsed.authority 176 | if parsed.host then 177 | authority = parsed.host 178 | if parsed.port then authority = authority .. ":" .. parsed.port end 179 | local userinfo = parsed.userinfo 180 | if parsed.user then 181 | userinfo = parsed.user 182 | if parsed.password then 183 | userinfo = userinfo .. ":" .. parsed.password 184 | end 185 | end 186 | if userinfo then authority = userinfo .. "@" .. authority end 187 | end 188 | if authority then url = "//" .. authority .. url end 189 | if parsed.scheme then url = parsed.scheme .. ":" .. url end 190 | if parsed.fragment then url = url .. "#" .. parsed.fragment end 191 | -- url = string.gsub(url, "%s", "") 192 | return url 193 | end 194 | 195 | -- Builds a absolute URL from a base and a relative URL according to RFC 2396 196 | function Url.absolute(base_url, relative_url) 197 | if base.type(base_url) == "table" then 198 | base_parsed = base_url 199 | base_url = Url.build(base_parsed) 200 | else 201 | base_parsed = Url.parse(base_url) 202 | end 203 | local relative_parsed = Url.parse(relative_url) 204 | if not base_parsed then return relative_url 205 | elseif not relative_parsed then return base_url 206 | elseif relative_parsed.scheme then return relative_url 207 | else 208 | relative_parsed.scheme = base_parsed.scheme 209 | if not relative_parsed.authority then 210 | relative_parsed.authority = base_parsed.authority 211 | if not relative_parsed.path then 212 | relative_parsed.path = base_parsed.path 213 | if not relative_parsed.params then 214 | relative_parsed.params = base_parsed.params 215 | if not relative_parsed.query then 216 | relative_parsed.query = base_parsed.query 217 | end 218 | end 219 | else 220 | relative_parsed.path = absolute_path(base_parsed.path or "", 221 | relative_parsed.path) 222 | end 223 | end 224 | return Url.build(relative_parsed) 225 | end 226 | end 227 | 228 | -- Breaks a path into its segments, unescaping the segments 229 | function Url.parse_path(path) 230 | local parsed = {} 231 | path = path or "" 232 | --path = string.gsub(path, "%s", "") 233 | string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) 234 | for i = 1, #parsed do 235 | parsed[i] = Url.unescape(parsed[i]) 236 | end 237 | if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end 238 | if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end 239 | return parsed 240 | end 241 | 242 | -- Builds a path component from its segments, escaping protected characters. 243 | function Url.build_path(parsed, unsafe) 244 | local path = "" 245 | local n = #parsed 246 | if unsafe then 247 | for i = 1, n-1 do 248 | path = path .. parsed[i] 249 | path = path .. "/" 250 | end 251 | if n > 0 then 252 | path = path .. parsed[n] 253 | if parsed.is_directory then path = path .. "/" end 254 | end 255 | else 256 | for i = 1, n-1 do 257 | path = path .. protect_segment(parsed[i]) 258 | path = path .. "/" 259 | end 260 | if n > 0 then 261 | path = path .. protect_segment(parsed[n]) 262 | if parsed.is_directory then path = path .. "/" end 263 | end 264 | end 265 | if parsed.is_absolute then path = "/" .. path end 266 | return path 267 | end 268 | 269 | return Url -------------------------------------------------------------------------------- /tests/XMLElement.lua: -------------------------------------------------------------------------------- 1 | local XMLElement = require "feedparser.XMLElement" 2 | local lom = require "lxp.lom" 3 | 4 | local function req(a, b) 5 | local t = type(a) 6 | if t~=type(b) then return false end 7 | if t == 'table' then 8 | for i,v in pairs(a) do 9 | local eq = req(v, b[i]) 10 | if not eq then return nil end 11 | end 12 | return true 13 | elseif t == 'function' or t == 'userdata' then 14 | return true 15 | else 16 | return a == b 17 | end 18 | end 19 | 20 | local function dump(tbl) 21 | local function tcopy(t) local nt={}; for i,v in pairs(t) do nt[i]=v end; return nt end 22 | local function printy(thing, prefix, tablestack) 23 | local t = type(thing) 24 | if t == "nil" then return "nil" 25 | elseif t == "string" then return string.format('%q', thing) 26 | elseif t == "number" then return tostring(thing) 27 | elseif t == "table" then 28 | if tablestack[thing] then return string.format("%s (recursion)", tostring(thing)) end 29 | local kids, pre, substack = {}, "\t" .. prefix, (tablestack and tcopy(tablestack) or {}) 30 | substack[thing]=true 31 | for k, v in pairs(thing) do 32 | table.insert(kids, string.format('%s%s=%s,',pre,printy(k, ''),printy(v, pre, substack))) 33 | end 34 | return string.format("%s{\n%s\n%s}", tostring(thing), table.concat(kids, "\n"), prefix) 35 | else 36 | return tostring(thing) 37 | end 38 | end 39 | local ret = printy(tbl, "", {}) 40 | print(ret) 41 | return ret 42 | end 43 | 44 | local function filecontents(path) 45 | local f = io.open(path, 'r') 46 | if not f then return nil, path .. " is not a file or doesn't exist." end 47 | local res = f:read('*a') 48 | f:close() 49 | return res 50 | end 51 | 52 | print "consistency" 53 | local xml = assert(filecontents("tests/xml/simple.xml")) 54 | local l= assert(lom.parse(xml)) 55 | local root = assert(XMLElement.new(l)) 56 | assert(req(root, XMLElement.new(lom.parse(root:getXML())))) 57 | 58 | local feedxml = assert(filecontents("tests/xml/simple.xml")) 59 | local feedroot = XMLElement.new(assert(lom.parse(xml))) 60 | assert(req(feedroot, XMLElement.new(lom.parse(feedroot:getXML())))) 61 | 62 | print "children" 63 | local kids = root:getChildren('foo') 64 | assert(#kids==3) 65 | assert(#root:getChildren({'selfclosing', 'bacon:strip'}==4)) 66 | for i, el in ipairs(kids) do 67 | assert(getmetatable(root)==getmetatable(el)) 68 | assert(el:getTag()=='foo') 69 | end 70 | 71 | assert(#root:getChild('foo'):getChildren()==2) 72 | assert(root:getText()) 73 | 74 | print('blank element') 75 | local blanky = XMLElement.new() 76 | assert(blanky:getText()=='') 77 | 78 | print("descendants") 79 | assert(#root:getDescendants()==8) 80 | -------------------------------------------------------------------------------- /tests/dateparser.lua: -------------------------------------------------------------------------------- 1 | local dateparser = require "feedparser.dateparser" 2 | 3 | print("RFC2822") 4 | assert(dateparser.parse('Fri, 09 Jan 2009 07:16:10 GMT')==1231485370) 5 | assert(dateparser.parse('Fri, 09 Jan 2009 07:16:10 EST')==1231503370) 6 | 7 | assert(dateparser.parse('09 Feb 2012 07:16:10 +0000')==1328771770) 8 | assert(dateparser.parse('09 Feb 2012 07:16:10 +1201')==1328728510) 9 | assert(dateparser.parse('09 Feb 2012 07:16:10 +0752')==1328743450) 10 | assert(dateparser.parse('01 Jan 1970 00:00:00 +0101')==-3660) 11 | assert(dateparser.parse('09 Feb 2012 07:16:10 +0101')==1328768110) 12 | assert(dateparser.parse('1 Jul 1980 00:00:00 GMT')==331257600) 13 | assert(dateparser.parse('1 Jul 1980 00:00:00 PDT')==331282800) 14 | 15 | print("W3CDTF") 16 | assert(dateparser.parse('1970-01-01T00:00:00Z')==0) 17 | assert(dateparser.parse('1970-02-01T00:15:00Z')==2679300) 18 | assert(dateparser.parse('2003-12-31T10:14:55.117Z')==1072865695.117) 19 | assert(dateparser.parse('1980-01-09')==316224000) 20 | assert(dateparser.parse('1980-03-01')==320716800) 21 | assert(dateparser.parse('2003-12-31T10:14:55-08:00')==1072894495) 22 | 23 | 24 | print("before unix epoch") 25 | if dateparser.parse('09 Dec 1965 07:45:51 PDT')~=-128164449 26 | or dateparser.parse('1966-06-23T1:12Z')~=-111278880 then 27 | print("Warning: your OS doesn't do times before unix epoch properly. Not strictly a dateparser test error.\nJust thought you should know.") 28 | end 29 | -------------------------------------------------------------------------------- /tests/feedparser.lua: -------------------------------------------------------------------------------- 1 | local feedparser=require "feedparser" 2 | --this is by no means comprehensive 3 | 4 | if _VERSION:sub(-3) < "5.3" then 5 | print("using " .. _VERSION .. ", a feedparser global is expected.") 6 | assert(_G.feedparser==feedparser) 7 | else 8 | print("using " .. _VERSION .. ", feedparser should not set any globals.") 9 | assert(_G.feedparser==nil) 10 | end 11 | 12 | local function filecontents(path) 13 | local f = io.open(path, 'r') 14 | if not f then return nil, path .. " is not a file or doesn't exist." end 15 | local res = f:read('*a') 16 | f:close() 17 | return res 18 | end 19 | 20 | print("atom") 21 | local res = assert(feedparser.parse(assert(filecontents("tests/feeds/atom1.0-1.xml")))) 22 | assert(#res.entries==2) 23 | assert(res.version=="atom10") 24 | assert(res.feed.author=="John Doe") 25 | assert(res.feed.link=="http://example.org/") 26 | 27 | local a = assert(feedparser.parse(assert(filecontents("tests/feeds/atom-ob.xml")))) 28 | assert(a.version=="atom10") 29 | assert(a.format=="atom") 30 | assert(#a.entries==10) 31 | print("rss") 32 | local res = assert(feedparser.parse(assert(filecontents("tests/feeds/rss-reddit.xml")))) 33 | assert(#res.entries==100) 34 | assert(res.feed.title=="reddit.com: what's new online") 35 | 36 | print("rss2") 37 | local res = assert(feedparser.parse(assert(filecontents("tests/feeds/rss-userland-dawkins.xml")))) 38 | 39 | print("rebasing and url resolution") 40 | local feed = [[ 41 | 42 | Example Feed 43 | 44 | 2003-12-13T18:30:02Z 45 | 46 | John Doe 47 | 48 | urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 49 | 50 | 51 | The Exciting conclusion of Atom-Powered Robots Run Amok 52 | 53 | urn:uuid:1225c695-cfb8-4ebb-whatever 54 | 2003-12-13T18:30:02Z 55 | Some text. 56 | 57 | 58 | Atom-Powered Robots Run Amok, part One 59 | 60 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 61 | 2002-10-01T18:30:00Z 62 | arr, texty 63 | 64 | 65 | ]] 66 | local res = assert(feedparser.parse(feed)) 67 | assert(res.feed.link=="http://foo.com/excellent") 68 | 69 | local feed = [[ 70 | 71 | Example Feed 72 | 73 | 2003-12-13T18:30:02Z 74 | 75 | John Doe 76 | 77 | urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 78 | 79 | 80 | The Exciting conclusion of Atom-Powered Robots Run Amok 81 | 82 | urn:uuid:1225c695-cfb8-4ebb-whatever 83 | 2003-12-13T18:30:02Z 84 | Some text. 85 | 86 | 87 | Atom-Powered Robots Run Amok, part One 88 | 89 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 90 | 2002-10-01T18:30:00Z 91 | arr, texty 92 | 93 | 94 | ]] 95 | local res = assert(feedparser.parse(feed, "http://bacon.net")) 96 | assert(res.feed.link=="http://bacon.net/excellent") 97 | -------------------------------------------------------------------------------- /tests/feeds/atom-ob.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Overcoming Bias 4 | 5 | 6 | tag:typepad.com,2003:weblog-549792 7 | 2009-01-18T04:03:29-05:00 8 | A forum for those serious about trying to overcome their own biases in beliefs and actions. 9 | TypePad 10 | 11 | In Praise of Boredom 12 | 13 | 14 | tag:typepad.com,2003:post-61530602 15 | 2009-01-18T04:03:29-05:00 16 | 2009-01-18T12:21:05-05:00 17 | Previously in series: Seduced by ImaginationIf I were to make a short list of the most important human qualities -- and yes, this is a fool's errand, because human nature is immensely complicated, and we don't even notice all the... 18 | 19 | Eliezer Yudkowsky 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

Previously in seriesSeduced by Imagination

If I were to make a short list of the most important human qualities -

- and yes, this is a fool's errand, because human nature is immensely complicated, and we don't even notice all the tiny tweaks that fine-tune our moral categories, and who knows how our attractors would change shape if we eliminated a single human emotion -

- but even so, if I had to point to just a few things and say, "If you lose just one of these things, you lose most of the expected value of the Future; but conversely if an alien species independently evolved just these few things, we might even want to be friends" -

- then the top three items on the list would be sympathy, boredom and consciousness.

Boredom is a subtle-splendored thing.  You wouldn't want to get bored with breathing, for example - even though it's the same motions over and over and over and over again for minutes and hours and years and decades.

Now I know some of you out there are thinking, "Actually, I'm quite bored with breathing and I wish I didn't have to," but then you wouldn't want to get bored with switching transistors.

According to the human value of boredom, some things are allowed to be highly repetitive without being boring - like obeying the same laws of physics every day.

Conversely, other repetitions are supposed to be boring, like playing the same level of Super Mario Brothers over and over and over again until the end of time.  And let us note that if the pixels in the game level have a slightly different color each time, that is not sufficient to prevent it from being "the same damn thing, over and over and over again".

Once you take a closer look, it turns out that boredom is quite interesting.

28 | 29 |

One of the key elements of boredom was suggested in "Complex Novelty":  If your activity isn't teaching you insights you didn't already know, then it is non-novel, therefore old, therefore boring.

But this doesn't quite cover the distinction.  Is breathing teaching you anything?  Probably not at this moment, but you wouldn't want to stop breathing.  Maybe you'd want to stop noticing your breathing, which you'll do as soon as I stop drawing your attention to it.

I'd suggest that the repetitive activities which are allowed to not be boring fall into two categories:

    30 | 31 |
  • Things so extremely low-level, or with such a small volume of possibilities, that you couldn't avoid repeating them even if you tried; but which are required to support other non-boring activities.  You know, like breathing, or obeying the laws of physics, or cell division - that sort of thing.
  • 32 |
  • Things so high-level that their "stability" still implies an immense space of specific possibilities, yet which are tied up with our identity or our values.  Like thinking, for example.
  • 33 |
34 |

Let me talk about that second category:

Suppose you were unraveling the true laws of physics and discovering all sorts of neat stuff you hadn't known before... when suddenly you got bored with "changing your beliefs based on observation".  You are sick of anything resembling "Bayesian updating" - it feels like playing the same video game over and over.  Instead you decide to believe anything said on 4chan.

Or to put it another way, suppose that you were something like a sentient chessplayer - a sentient version of Deep Blue.  Like a modern human, you have no introspective access to your own algorithms.  Each chess game appears different - you play new opponents and steer into new positions, composing new strategies, avoiding new enemy gambits.  You are content, and not at all bored; you never appear to yourself to be doing the same thing twice - it's a different chess game each time.

But now, suddenly, you gain access to, and understanding of, your own chess-playing program.  Not just the raw code; you can monitor its execution.  You can see that it's actually the same damn code, doing the same damn thing, over and over and over again.  Run the same damn position evaluator.  Run the same damn sorting algorithm to order the branches.  Pick the top branch, again.  Extend it one position forward, again.  Call the same damn subroutine and start over.

I have a small unreasonable fear, somewhere in the back of my mind, that if I ever do fully understand the algorithms of intelligence, it will destroy all remaining novelty - no matter what new situation I encounter, I'll know I can solve it just by being intelligent, the same damn thing over and over.  All novelty will be used up, all existence will become boring, the remaining differences no more important than shades of pixels in a video game.  Other beings will go about in blissful unawareness, having been steered away from studying this forbidden cognitive science.  But I, having already thrown myself on the grenade of AI, will face a choice between eternal boredom, or excision of my forbidden knowledge and all the memories leading up to it (thereby destroying my existence as Eliezer, more or less).

Now this, mind you, is not my predictive line of maximum probability.  To understand abstractly what rough sort of work the brain is doing, doesn't let you monitor its detailed execution as a boring repetition.  I already know about Bayesian updating, yet I haven't become bored 35 | with the act of learning.  And a self-editing mind can quite reasonably 36 | exclude certain levels of introspection from boredom, just like 37 | breathing can be legitimately excluded from boredom.  (Maybe these top-level cognitive algorithms ought also to be excluded from perception - if something is stable, why bother seeing it all the time?)

No, it's just a cute little nightmare, which I thought made a nice illustration of this proposed principle:

That the very top-level things (like Bayesian updating, or attaching value to sentient minds rather than paperclips) and the very low-level things (like breathing, or switching transistors) are the things we shouldn't get bored with.  And the mid-level things between, are where we should seek novelty.  (To a first approximation, the novel is the inverse of the learned; it's something with a learnable element not yet covered by previous insights.)

Now this is probably not exactly how our current emotional circuitry of boredom works.  That, I expect, would be hardwired relative to various sensory-level definitions of predictability, surprisingness, repetition, attentional salience, and perceived effortfulness.

But this is Fun Theory, so we are mainly concerned with how boredom should work in the long run.

Humanity acquired boredom the same way as we acquired the rest of our emotions: the godshatter idiom whereby evolution's instrumental policies became our own terminal values, pursued for their own sake: sex is fun even if you use birth control.  Evolved aliens might, or might not, acquire roughly the same boredom in roughly the same way.

Do not give into the temptation of universalizing anthropomorphic values, and think:  "But any rational agent, regardless of its utility function, will face the exploration/exploitation tradeoff, and will therefore occasionally get bored with exploiting, and go exploring."

Our emotion of boredom is a way of exploring, but not the only way for an ideal optimizing agent.

The idea of a steady trickle of mid-level novelty is a human 38 | 39 | terminal value, not something we do for the sake of something else.  40 | Evolution might have originally given it to us in order to have us explore as well as exploit.  But now we explore for its own sake.  That 41 | steady trickle of novelty is a terminal value to us; it is not the most efficient instrumental method for exploring and exploiting.

Suppose you were dealing with something like an expected paperclip maximizer - something that might use quite complicated instrumental policies, but in the service of a utility function that we would regard as simple, with a single term compactly defined.

Then I would expect the exploration/exploitation tradeoff to go something like as follows:  The paperclip maximizer would assign some resources to cognition that searched for more efficient ways to make paperclips, or harvest resources from stars.  Other resources would be devoted to the actual harvesting and paperclip-making.  (The paperclip-making might not start until after a long phase of harvesting.)  At every point, the most efficient method yet discovered - for resource-harvesting, or paperclip-making - would be used, over and over and over again.  It wouldn't be boring, just maximally instrumentally efficient.

In the beginning, lots of resources would go into preparing for efficient work over the rest of time.  But as cognitive resources yielded diminishing returns in the abstract search for efficiency improvements, less and less time would be spent thinking, and more and more time spent creating paperclips.  By whatever the most efficient known method, over and over and over again.

(Do human beings get less easily bored as we grow older, more tolerant of repetition, because any further discoveries are less valuable, because we have less time left to exploit them?)

If we run into aliens who don't share our version of boredom - a steady trickle of mid-level novelty as a terminal preference - then perhaps every alien throughout their civilization will just be playing the most exciting level of the most exciting video game ever discovered, over and over and over again.  Maybe with nonsentient AIs taking on the drudgework of searching for a more exciting video game.  After all, without an inherent preference for novelty, exploratory attempts will usually have less expected value than exploiting the best policy previously encountered.  And that's if you explore by trial at all, as opposed to using more abstract and efficient thinking.

Or if the aliens are rendered non-bored by seeing pixels of a slightly different shade - if their definition of sameness is more specific than ours, and their boredom less general - then from our perspective, most of their civilization will be doing the human::same thing over and over again, and hence, be very human::boring.

Or maybe if the aliens have no fear of life becoming too simple and repetitive, they'll just collapse themselves into orgasmium.

And if our version of boredom is less strict than that of the aliens, maybe they'd take one look at one day in the life of one member of our civilization, and never bother looking at the rest of us.  From our perspective, their civilization would be needlessly chaotic, and so entropic, lower in what we regard as quality; they wouldn't play the same game for long enough to get good at it.

But if our versions of boredom are similar enough - terminal preference for a stream of mid-level novelty defined relative to learning insights not previously possessed - then we might find our civilizations mutually worthy of tourism.  Each new piece of alien art would strike us as lawfully creative, high-quality according to a recognizable criterion, yet not like the other art we've already seen.

It is one of the things that would make our two species ramen rather than varelse, to invoke the Hierarchy of Exclusion.  And I've never seen anyone define those two terms well, including Orson Scott Card who invented them; but it might be something like "aliens you can get along with, versus aliens for which there is no reason to bother trying".

42 | 43 |
44 | 45 | 46 |
47 | 48 | Beware Detached Detail 49 | 50 | 51 | tag:typepad.com,2003:post-61531546 52 | 2009-01-17T21:15:00-05:00 53 | 2009-01-18T09:21:46-05:00 54 | Yesterday I talked about how "social minds must both make good decisions, and present good images to others" and suggested "the near-far brain division can be handy when facing this problem; let the far system focus more on image, and... 55 | 56 | Robin Hanson 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |

Yesterday I talked about how "social minds must both make good decisions, and present good images to others" and suggested "the near-far brain division can be handy when facing this problem; let the far system focus more on image, and the near system focus more on decisions." But I didn't follow this thought very far down the game tree. To an economist going down the game tree is like going down the rabbit hole; it shows us just how deep and strange are the underlying drivers of top behavior.

If our far thoughts are more distorted to present good images, then the next step down the rabbit hole is this: to judge how we will typically act, others should prefer to see our near thoughts, at least if they can distinguish near versus far thoughts. After all, near thoughts drive most day to day actions. And we should each look more to our own near thoughts to judge our own sincerity. 65 | 66 |

Once we evolved to weigh near others' thoughts more heavily, the next step would be to look for cheap ways to have good-looking near-thoughts, without paying the full price of distorting important actions. That is, our mind designer would look for ways to show "detached" near thoughts, consistent with good-image far-thoughts, but not actually impacting much on important near decisions. This could be accomplished by vivid engaging detail that can clearly occupy our near thought systems, but which isn't much connected to substantial personal decisions. 67 | 68 | 69 |

70 | 71 |

For example, abstract religious beliefs are usually tied to specific concrete, but largely irrelevant, details. The gods and prophets have names and birth-dates and stories of who did what when. Churches have pictures and idols people and events, and religious rituals are full of rich sensory details, like incense, plush materials, stained-glass windows, and angelic singing. Daily routines regarding what to eat and when to pray and what to wear all let you show how much near thought there is in your religious thought.  Of course most of these details hardly matter to your important decisions; they let you assure yourself you will be a good Samaritan, without actually much influencing whether you be a good Samaritan. 72 | 73 |

Similarly, fiction lets us tie lots of concrete detail to our abstract social beliefs about what sorts of people are good or bad, who helps who how much, and which of them tend to win in the end.  Stories presented as plays, movies, or video games have especially rich sensory detail, to give us experiences not just of abstracting having certain far reactions, but of having our near systems fully engaged and in agreement with our far expectations of what one should do in various situations. 74 | 75 |

A movie can let you feel not only that people in distant times and places should fight Nazis or free slaves, but that if you were actually in such situations with near systems fully engaged, you would actually do such things. But of course since the movie's scenario has little overlap with your real life, there is little risk that near habits acquired would interfere with your usual near actions. You rarely watch movies about, say, helping poor neighbors or illegal immigrants, since those stories are less detached from your ordinary life.  There is a reason they call it "escapism," after all. 76 | 77 |

This detached detail dodge may explain why inside views seem worse at schedule forecasting than outside views.  Yes inside-view schedulers consider more detail that engages near mental systems, but I suspect this is mostly detached detail, driven as with fiction detail more by far thought agendas than by consistency with relevant personal detail.  Little girls may imagine their future wedding in vivid detail, without that being much constrained by actual weddings they have seen. 78 | 79 | 80 |

Don't assure yourself that your vivid near thoughts mean you are being more realistic, if those vivid thoughts are little connected with real personal experience. It might just be detached detail, there more to flesh out far thoughts than to advise important decisions. When you stand at a real street corner looking to see if it is safe to cross, that is attached detail; when you imagine being tempted by a grinning ten foot devil after fasting for weeks in the hot red desert, that is detached detail.

81 |
82 | 83 | 84 |
85 | 86 | Getting Nearer 87 | 88 | 89 | tag:typepad.com,2003:post-61505446 90 | 2009-01-17T04:28:54-05:00 91 | 2009-01-18T12:29:54-05:00 92 | Reply to: A Tale Of Two TradeoffsI'm not comfortable with compliments of the direct, personal sort, the "Oh, you're such a nice person!" type stuff that nice people are able to say with a straight face. Even if it would... 93 | 94 | Eliezer Yudkowsky 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |

Reply toA Tale Of Two Tradeoffs

I'm not comfortable with compliments of the direct, personal sort, the "Oh, you're such a nice person!" type stuff that nice people are able to say with a straight face.  Even if it would make people like me more - even if it's socially expected - I have trouble bringing myself to do it.  So, when I say that I read Robin Hanson's "Tale of Two Tradeoffs", and then realized I would spend the rest of my mortal existence typing thought processes as "Near" or "Far", I hope this statement is received as a due substitute for any gushing compliments that a normal person would give at this point.

Among other things, this clears up a major puzzle that's been lingering in the back of my mind for a while now.  Growing up as a rationalist, I was always telling myself to "Visualize!" or "Reason by simulation, not by analogy!" or "Use causal models, not similarity groups!"  And those who ignored this principle seemed easy prey to blind enthusiasms, wherein one says that A is good because it is like B which is also good, and the like.

But later, I learned about the Outside View versus the Inside View, and that people asking "What rough class does this project fit into, and when did projects like this finish last time?" were much more accurate and much less optimistic than people who tried to visualize the when, where, and how of their projects.  And this didn't seem to fit very well with my injunction to "Visualize!"

So now I think I understand what this principle was actually doing - it was keeping me in Near-side mode and away from Far-side thinking.  And it's not that Near-side mode works so well in any absolute sense, but that Far-side mode is so much more pushed-on by ideology and wishful thinking, and so casual in accepting its conclusions (devoting less computing power before halting).

103 | 104 |

An example of this might be the balance between offensive and defensive 105 | nanotechnology, where I started out by - basically - just liking nanotechnology; until I got involved in a discussion about the particulars of nanowarfare, and noticed that people were postulating 106 | crazy things to make defense win.  Which made me realize and say, "Look, the 107 | balance between offense and defense has been tilted toward offense ever 108 | since the invention of nuclear weapons, and military nanotech could use nuclear weapons, and I don't see how you're going to build a molecular barricade against that."

Are the particulars of that discussion likely to be, well, correct?  Maybe not.  But so long as I wasn't thinking of any particulars, my brain had free reign to just... import whatever affective valence the word "nanotechnology" had, and use that as a snap judgment of everything.

You can still be biased about particulars, of course.  You can insist that nanotech couldn't possibly be radiation-hardened enough to manipulate U-235, which someone tried as a response (fyi: this is extremely silly).  But in my case, at least, something about thinking in particulars...

...just snapped me out of the trance, somehow.

When you're thinking using very abstract categories - rough classes low on computing power - about things distant from you, then you're also - if Robin's hypothesis is correct - more subject to ideological bias.  Together this implies you can cherry-pick those very loose categories to put X together with whatever "similar" Y is ideologically convenient, as in the old saw that "atheism is a religion" (and not playing tennis is a sport).

But the most frustrating part of all, is the casualness of it - the way that ideologically convenient Far thinking is just thrown together out of whatever ingredients come to hand.  The ten-second dismissal of cryonics, without any attempt to visualize how much information is preserved by vitrification and could be retrieved by a molecular-level scan.  Cryonics just gets casually, perceptually classified as "not scientifically verified" and tossed out the window.  Or "what if you wake up in Dystopia?" and tossed out the window.  Far thinking is casual - that's the most frustrating aspect about trying to argue with it. 109 | 110 |

This seems like an argument for writing fiction with lots of concrete details if you want people to take a subject seriously and think about it in a less biased way.  This is not something I would have thought based on my previous view.

Maybe cryonics advocates really should focus on writing fiction stories that turn on the gory details of cryonics, or viscerally depict the regret of someone who didn't persuade their mother to sign up.  (Or offering prizes to professionals who do the same; writing fiction is hard, writing SF is harder.)

But I'm worried that, for whatever reason, reading concrete fiction is a special case that doesn't work to get people to do Near-side thinking.

Or there are some people who are inspired to Near-side thinking by fiction, and only these can actually be helped by reading science fiction.

Maybe there are people who encounter big concrete detailed fictions process them in a Near way - the sort of people who notice plot holes.  And others who just "take it all in stride", casually, so that however much concrete fictional "information" they encounter, they only process it using casual "Far" thinking.  I wonder if this difference has more to do with upbringing or genetics.  Either way, it may lie at the core of the partial yet statistically outstanding correlation between careful futurists and science fiction fans.

I expect I shall be thinking about this for a while.

111 | 112 |
113 | 114 | 115 |
116 | 117 | A Tale Of Two Tradeoffs 118 | 119 | 120 | tag:typepad.com,2003:post-61445194 121 | 2009-01-16T06:00:00-05:00 122 | 2009-01-18T09:46:26-05:00 123 | The design of social minds involves two key tradeoffs, which interact in an important way. The first tradeoff is that social minds must both make good decisions, and present good images to others. Our thoughts influence both our actions and... 124 | 125 | Robin Hanson 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |

The design of social minds involves two key tradeoffs, which interact in an important way.

134 | The first tradeoff is that social minds must both make good decisions, and present good images to others.  Our thoughts influence both our actions and what others think of us.  It would be expensive to maintain two separate minds for these two purposes, and even then we would have to maintain enough consistency to convince outsiders a good-image mind was in control. It is cheaper and simpler to just have one integrated mind whose thoughts are a compromise between these two ends.

135 | When possible, mind designers should want to adjust this decision-image tradeoff by context, depending on the relative importance of decisions versus images in each context.  But it might be hard to find cheap effective heuristics saying when images or decisions matter more.

136 | The second key tradeoff is that minds must often think about the same sorts of things using different amounts of detail.  Detailed representations tend to give more insight, but require more mental resources.  In contrast, sparse representations require fewer resources, and make it easier to abstractly compare things to each other.  For example, when reasoning about a room a photo takes more work to study but allows more attention to detail; a word description contains less info but can be processed more quickly, and allows more comparisons to similar rooms. 137 | 138 |

139 |

140 | It makes sense to have your mental models use more detail when what they model is closer to you in space and time, and closer to you in your social world; such things tend to be more important to you.  It also makes sense to use more detail for real events over hypothetical ones, for high over low probability events, for trend deviations over trend following, and for thinking about how to do something over why to do it.  So it makes sense to use detail thinking for "near", and sparse thinking for "far", in these ways. 

141 | It can make sense to have specialized mental systems for these different approaches, i.e., systems best at reasoning from detailed representations, versus systems best at reasoning from sparse abstractions.  When something became important enough to think about at all you would first use sparse systems, graduating to detail systems when that thing became important enough to justify the added resources.  Even then you might continue to reason about it using sparse systems, at least if you could sufficiently coordinate the two kinds of systems.

142 | A non-social mind, caring only about good personal decisions, would want consistency between near and far thoughts.  To be consistent, estimates made by sparse approaches should equal the average of estimates made when both sparse and detail approaches contribute.  A social mind would also want such consistency when sparse and detail tasks had the same tradeoffs between decisions and images.  But when these tradeoffs differ, inconsistency can be more attractive. 

143 | The important interaction between these two key tradeoffs is this: near versus far seems to correlate reasonably well with when good decisions matter more, relative to good images.  Decision consequences matter less for hypothetical, fictional, and low probability events.  Social image matters more, relative to decision consequences, for opinions about what I should do in the distant future, or for what they or "we" should do now.  Others care more about my basic goals than about how exactly I achieve them, and they care especially about my attitudes toward those people.  Also, widely shared topics are better places to demonstrate mental abilities.

144 | Thus a good cheap heuristic seems to be that image matters more for "far" thoughts, relative to decisions mattering more for "near" thoughts.  And so it makes sense for social minds to allow inconsistencies between near and far thinking systems.  Instead of having both systems produce the same average estimates, it can make sense for sparse estimates to better achieve a good image, while detail estimates better achieve good decisions. 

145 | And this seems to be just what the human mind does.  The human mind seems to have different "near" and "far" mental systems, apparently implemented in distinct brain regions, for detail versus abstract reasoning.  Activating one of these systems on a topic for any reason makes other activations of that system on that topic more likely; all near thinking tends to evoke other near thinking, while all far thinking tends to evoke other far thinking. 

146 | These different human mental systems tend to be inconsistent in giving systematically different estimates to the same questions, and these inconsistencies seem too strong and patterned to be all accidental.  Our concrete day-to-day decisions rely more on near thinking, while our professed basic values and social opinions, especially regarding fiction, rely more on far thinking.  Near thinking better helps us work out complex details of how to actually get things done, while far thinking better presents our identity and values to others.  Of course we aren't very aware of this hypocrisy, as that would undermine its purpose; so we habitually assume near and far thoughts are more consistent than they are. 

147 | These near-far inconsistencies seems to me to reasonably explain puzzles like:

    148 | 149 |
  • 150 | we value particular foreign-born associates, but oppose foreign immigration
  • 151 |
  • 152 | we say we want to lose weight, but actually don't exercise more or eat less
  • 153 |
  • 154 | we say we care about distant future folk, but don't save money for them
  • 155 |
156 |

157 | So which of near or far thinking is our "true" thinking?  Perhaps neither; perhaps we really contain an essential contradiction, which we don't want to admit, much less resolve.

Added:  The key puzzle I'm trying to address here is the fact that hypocrisy is hard.  It is hard enough to manage a mind with coherent opinions across a wide range of topics.  To manage two coherent systems of opinions, one for decisions and one for image, and then only let them differ where others can't see, that seems really hard.  I'm saying the near-far brain division can be handy when facing this problem; let the far system focus more on image, and the near system focus more on decisions.

158 | 159 |
160 | 161 | 162 |
163 | 164 | Seduced by Imagination 165 | 166 | 167 | tag:typepad.com,2003:post-61450634 168 | 2009-01-15T22:10:22-05:00 169 | 2009-01-17T14:14:36-05:00 170 | Previously in series: Justified Expectation of Pleasant Surprises"Vagueness" usually has a bad name in rationality - connoting skipped steps in reasoning and attempts to avoid falsification. But a rational view of the Future should be vague, because the information we... 171 | 172 | Eliezer Yudkowsky 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Previously in seriesJustified Expectation of Pleasant Surprises

"Vagueness" usually has a bad name in rationality - connoting skipped steps in reasoning and attempts to avoid falsification.  But a rational view of the Future should be vague, because the information we have about the Future is weak.  Yesterday I argued that justified vague hopes might also be better hedonically than specific foreknowledge - the power of pleasant surprises.

But there's also a more severe warning that I must deliver:  It's not a good idea to dwell much on imagined pleasant futures, since you can't actually dwell in them.  It can suck the emotional energy out of your actual, current, ongoing life.

Epistemically, we know the Past much more specifically than the Future.  But also on emotional grounds, it's probably wiser to compare yourself to Earth's past, so you can see how far we've come, and how much better we're doing.  Rather than comparing your life to an imagined future, and thinking about how awful you've got it Now.

Having set out to explain George Orwell's observation that no one can seem to write about a Utopia where anyone would want to live - having laid out the various Laws of Fun that I believe are being violated in these dreary Heavens - I am now explaining why you shouldn't apply this knowledge to invent an extremely seductive Utopia and write stories set there.  That may suck out your soul like an emotional vacuum cleaner. 181 | 182 |

183 |

I briefly remarked on this phenomenon earlier, and someone said, "Define 'suck out your soul'."  Well, it's mainly a tactile thing: you can practically feel the pulling sensation, if your dreams wander too far into the Future.  It's like something out of H. P. Lovecraft:  The Call of Eutopia.  A professional hazard of having to stare out into vistas that humans were meant to gaze upon, and knowing a little too much about the lighter side of existence.

But for the record, I will now lay out the components of "soul-sucking", that you may recognize the bright abyss and steer your thoughts away:

    184 | 185 |
  • Your emotional energy drains away into your imagination of Paradise:
      186 |
    • You find yourself thinking of it more and more often.
    • 187 |
    • The actual challenges of your current existence start to seem less interesting, less compelling; you think of them less and less.
    • 188 |
    • Comparing everything to your imagined perfect world heightens your annoyances and diminishes your pleasures.
    • 189 |
    190 |
  • 191 |
  • You go into an affective death spiral around your imagined scenario; you're reluctant to admit anything bad could happen on your assumptions, and you find more and more nice things to say.
  • 192 |
  • Your mind begins to forget the difference between fiction and real 193 | life:
      194 | 195 |
    • You originally made many arbitrary or iffy choices in constructing 196 | your scenario.  You forget that the Future is actually more 197 | unpredictable than this, and that you made your choices using limited 198 | foresight and merely human optimizing ability.
    • 199 |
    • You forget that, in real life, at least some of your amazing good ideas are guaranteed not to work as well as they do in your imagination.
    • 200 |
    • You start wanting the exact specific Paradise you imagined, and worrying about the disappointment if you don't get that exact thing.
    • 201 |
    202 | 203 |
  • 204 |
205 |

Hope can be a dangerous thing.  And when you've just been hit hard - at the moment when you most need 206 | hope to keep you going - that's also when the real world seems most 207 | painful, and the world of imagination becomes most seductive.

It's a balancing act, I think.  One needs enough Fun Theory to truly and legitimately justify hope in the future.  But not a detailed vision so seductive that it steals emotional energy from the real life and real challenge of creating that future.  You need "a light at the end of the secular rationalist tunnel" as Roko put it, but you don't want people to drift away from their bodies into that light.

So how much light is that, exactly?  Ah, now that's the issue.

I'll start with a simple and genuine question:  Is what I've already said, enough?

Is knowing the abstract fun theory and being able to pinpoint the exact flaws in previous flawed Utopias, enough to make you look forward to tomorrow?  Is it enough to inspire a stronger will to live?  To dispel worries about a long dark tea-time of the soul?  Does it now seem - on a gut level - that if we could really build an AI and really shape it, the resulting future would be very much worth staying alive to see?

208 | 209 |
210 | 211 | 212 |
213 | 214 | Data On Fictional Lies 215 | 216 | 217 | tag:typepad.com,2003:post-61383844 218 | 2009-01-15T06:00:00-05:00 219 | 2009-01-17T12:57:58-05:00 220 | A speculator paper analyses a dataset of 519 Victorian literature experts describing 382 characters from 201 canonical British novels of the nineteenth century. Characters were described by gender, as major or minor, as good or bad, by role (protagonist, antagonist,... 221 | 222 | Robin Hanson 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 |

A speculator paper analyses a dataset of 519 Victorian literature experts describing 382 characters from 201 canonical British novels of the nineteenth century.  Characters were described by gender, as major or minor, as good or bad, by role (protagonist, antagonist, friend of p, friend of a, or other), by a five factor personality type (from a ten-question instrument), as their (5-point-scale) degree of twelve different motives (converted to five factors: social dominance, constructive effort, romance, nurture, subsistence), and as the degree of ten different emotions they arouse in readers (converted to three factors: dislike, sorrow, interest). Experts agreed 87% of the time.  They found:

231 |
Antagonists virtually personify Social Dominance - the self-interested pursuit of wealth, prestige, and power. In these novels, those ambitions are sharply segregated from prosocial and culturally acquisitive dispositions. Antagonists are not only selfish and unfriendly but also undisciplined, emotionally unstable, and intellectually dull. Protagonists, in contrast, display motive dispositions and personality traits that exemplify strong personal development and healthy social adjustment. Protagonists are agreeable, conscientious, emotionally stable, and open to experience. ... The male protagonists in this study are relatively moderate, mild characters. They are introverted and agreeable, and they do not seek to dominate others socially. They are pleasant and conscientious, and they are also curious and alert. They are attractive characters, but they are not very assertive or aggressive characters. ...
232 |
233 |

In Hierarchy in the Forest: The Evolution of Egalitarian Behavior, Boehm (1999)... argues that ... humans developed a special capacity, ... for enforcing moralistic or altruistic norms. By enforcing these norms, humans succeed in controlling "free riders" or "cheaters," and they thus make it possible for genuinely altruistic genes to survive within a social group. Such altruistic dispositions, enforced by punishing defectors, would enable social groups to compete more successfully against other groups and would thus make "group selection" or "multi-level selection" an effective force in subsequent human evolution. ...

If Boehm and others are correct, ... by derogating dominance and enacting the triumph of the communitarian ethos, ... agonistic structure in the novels would articulate real features of human nature, but like culture in general, the novels would exaggerate the magnitude of those features.  Agonistic structure in these novels seems to serve as a medium for readers to participate vicariously in an egalitarian social ethos. ... as prosthetic extensions of social interactions that in non-literate cultures require face-to-face interaction. ...

234 | Could it not plausibly be argued that the novels merely depict social dynamics as they actually occur in the real world? If that were the case, one would have no reason to suppose that the novels mediate psychological processes in the community of readers. The novels might merely serve readers' need to gain realistic information about the larger patterns of social life. To assess the cogency of this challenge, consider the large-scale patterns revealed in the present study and ask whether those patterns plausibly reflect social reality: 235 | 236 |

The world is in reality divided into two main kinds of people. One kind is motivated exclusively by the desire for wealth, power, and prestige. These people have no affiliative dispositions whatsoever. Moreover, they are disagreeable, emotionally unstable, undisciplined, and narrow minded. The second kind of people, in contrast, have almost no desire for wealth, power, and prestige. They are animated by the purest and most self-forgetful dispositions for nurturing kin and helping non-kin. Moreover, they are agreeable, emotionally stable, conscientious, and open-minded. Life consists in a series of clear-cut confrontations between these two kinds of people. Fortunately, the second set almost always wins, and lives happily ever after. This is reality, and novels do nothing except depict this reality in a true and faithful way. 237 |
In our view, this alternative hypothesis fails of conviction. The novels do contain a vast fund of realistic social depiction and profound psychological analysis. In their larger imaginative structures, though, the novels evidently do not just represent human nature; they evoke certain impulses of human nature. Vicarious participation in the novel stirs up the reader's impulses to derogate dominance in others and to affirm one's identity as a positive, contributing member of his or her social group. It may not be too much of a leap to suggest that the emotional impulses aroused by the novel carry over when the novel is put down, actually encouraging people to suppress dominance and cooperate with others in real life.

This agrees a lot with William Flesch's Comeuppance, but like it focuses too much on group selection, instead of individual selection, pressures.  As I suggested ten days ago,

Both religion and fiction serve to reassure our associates that we will 238 | be nice.  In addition to letting us show we can do hard things, and 239 | that we are tied to associates by doing the same things, religious 240 | beliefs show we expect the not nice to be punished by supernatural 241 | powers, and our favorite fiction shows the sort of people we think are 242 | heroes and villains, how often they are revealed or get their due 243 | reward, and so on.

Hat tip to Fortune Elkins.

244 | 245 |
246 | 247 | 248 |
249 | 250 | Justified Expectation of Pleasant Surprises 251 | 252 | 253 | tag:typepad.com,2003:post-61388720 254 | 2009-01-15T02:26:51-05:00 255 | 2009-01-16T22:14:11-05:00 256 | Previously in series: Eutopia is Scary I recently tried playing a computer game that made a major fun-theoretic error. (At least I strongly suspect it's an error, though they are game designers and I am not.)The game showed me -... 257 | 258 | Eliezer Yudkowsky 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 |

Previously in seriesEutopia is Scary 267 | 268 |

I recently tried playing a computer game that made a major fun-theoretic error.  (At least I strongly suspect it's an error, though they are game designers and I am not.)

The game showed me - right from the start of play - what abilities I could purchase as I increased in level.  Worse, there were many different choices; still worse, you had to pay a cost in fungible points to acquire them, making you feel like you were losing a resource...  But today, I'd just like to focus on the problem of telling me, right at the start of the game, about all the nice things that might happen to me later.

I can't think of a good experimental result that backs this up; but I'd expect that a pleasant surprise would have a greater hedonic impact, than being told about the same gift in advance.  Sure, the moment you were first told about the gift would be good news, a moment of pleasure in the moment of being told.  But you wouldn't have the gift in hand at that moment, which limits the pleasure.  And then you have to wait.  And then when you finally get the gift - it's pleasant to go from not having it to having it, if you didn't wait too long; but a surprise would have a larger momentary impact, I would think.

This particular game had a status screen that showed all my future class abilities at the start of the game - inactive and dark but with full information still displayed.  From a hedonic standpoint this seems like miserable fun theory.  All the "good news" is lumped into a gigantic package; the items of news would have much greater impact if encountered separately.  And then I have to wait a long time to actually acquire the abilities, so I get an extended period of comparing my current weak game-self to all the wonderful abilities I could have but don't.

Imagine living in two possible worlds.  Both worlds are otherwise rich in challenge, novelty, and other aspects of Fun.  In both worlds, you get smarter with age and acquire more abilities over time, so that your life is always getting better.

But in one world, the abilities that come with seniority are openly discussed, hence widely known; you know what you have to look forward to.

In the other world, anyone older than you will refuse to talk about certain aspects of growing up; you'll just have to wait and find out.

I ask you to contemplate - not just which world you might prefer to live in - but how much you might want to live in the second world, rather than the first.  I would even say that the second world seems more alive; when I imagine living there, my imagined will to live feels stronger.  I've got to stay alive to find out what happens next, right?

The idea that hope is important to a happy life, is hardly original with me - though I think it might not be emphasized quite enough, on the lists of things people are told they need.

I don't agree with buying lottery tickets, but I do think I understand why people do it.  I remember the times in my life when I had more or less belief that things would improve - that they were heading up in the near-term or mid-term, close enough to anticipate.  I'm having trouble describing how much of a difference it makes.  Maybe I don't need to describe that difference, unless some of my readers have never had any light at the end of their tunnels, or some of my readers have never looked forward and seen darkness.

If existential angst comes from having at least one deep problem in your life that you aren't thinking about explicitly, so that the pain which comes from it seems like a natural permanent feature - then the very first question I'd ask, to identify a possible source of that problem, would be, "Do you expect your life to improve in the near or mid-term future?"

Sometimes I meet people who've been run over by life, in much the same way as being run over by a truck.  Grand catastrophe isn't necessary to destroy a will to live.  The extended absence of hope leaves the same sort of wreckage.

People need hope.  I'm not the first to say it.

But I think that the importance of vague hope is underemphasized.

"Vague" is usually not a compliment among rationalists.  Hear "vague hopes" and you immediately think of, say, an alternative medicine herbal profusion whose touted benefits are so conveniently unobservable (not to mention experimentally unverified) that people will buy it for anything and then refuse to admit it didn't work.  You think of poorly worked-out plans with missing steps, or supernatural prophecies made carefully unfalsifiable, or fantasies of unearned riches, or...

But you know, generally speaking, our beliefs about the future should be vaguer than our beliefs about the past.  We just know less about tomorrow than we do about yesterday.

There are plenty of bad reasons to be vague, all sorts of suspicious reasons to offer nonspecific predictions, but reversed stupidity is not intelligence:  When you've eliminated all the ulterior motives for vagueness, your beliefs about the future should still be vague.

We don't know much about the future; let's hope that doesn't change for as long as human emotions stay what they are.  Of all the poisoned gifts a big mind could give a small one, a walkthrough for the game has to be near the top of the list.

What we need to maintain our interest in life, is a justified expectation of pleasant surprises.  (And yes, you can expect a surprise if you're not logically omniscient.)  This excludes the herbal profusions, the poorly worked-out plans, and the supernatural.  The best reason for this justified expectation is experience, that is, being pleasantly surprised on a frequent yet irregular basis.  (If this isn't happening to you, please file a bug report with the appropriate authorities.)

Vague justifications for believing in a pleasant specific outcome would be the opposite.

There's also other dangers of having pleasant hopes that are too specific - even if justified, though more often they aren't - and I plan to talk about that in the next post.

269 | 270 |
271 | 272 | 273 |
274 | 275 | Disagreement Is Near-Far Bias 276 | 277 | 278 | tag:typepad.com,2003:post-61326928 279 | 2009-01-14T10:30:00-05:00 280 | 2009-01-16T15:21:19-05:00 281 | Back in November I read this Science review by Nira Liberman and Yaacov Trope on their awkwardly-named "Construal level theory", and wrote a post I estimated "to be the most dense with useful info on identifying our biases I've ever... 282 | 283 | Robin Hanson 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 |

Back in November I read this Science review by Nira Liberman and Yaacov Trope on their awkwardly-named "Construal level theory", and wrote a post I estimated "to be the most dense with useful info on identifying our biases I've ever written": 292 |

[NEAR] All of these bring each other more to mind: here, now, me, us; trend-deviating likely real local events; concrete, context-dependent, unstructured, detailed, goal-irrelevant incidental features; feasible safe acts; secondary local concerns; socially close folks with unstable traits. 

293 | 294 |
[FAR] Conversely, all these bring each other more to mind: there, then, them; trend-following unlikely hypothetical global events; abstract, schematic, context-freer, core, coarse, goal-related features; desirable risk-taking acts, central global symbolic concerns, confident predictions, polarized evaluations, socially distant people with stable traits.  295 |

Since then I've become even more impressed with it, as it explains most biases I know and care about, including muddled thinking about economics and the future.  For example, Ross's famous "fundamental attribution error" is a trivial application. 

296 | The key idea is that when we consider the same thing from near versus far, different features become salient, leading our minds to different conclusions.  This is now my best account of disagreement.  We disagree because we explain our own conclusions via detailed context (e.g., arguments, analysis, and evidence), and others' conclusions via coarse stable traits (e.g., demographics, interests, biases).  While we know abstractly that we also have stable relevant traits, and they have detailed context, we simply assume we have taken that into account, when we have in fact done no such thing. 

297 | For example, imagine I am well-educated and you are not, and I argue for the value of education and you argue against it.  I find it easy to dismiss your view as denigrating something you do not have, but I do not think it plausible I am mainly just celebrating something I do have.  I can see all these detailed reasons for my belief, and I cannot easily see and appreciate your detailed reasons. 

298 | And this is the key error: our minds often assure us that they have taken certain factors into account when they have done no such thing.  I tell myself that of course I realize that I might be biased by my interests; I'm not that stupid.  So I must have already taken that possible bias into account, and so my conclusion must be valid even after correcting for that bias.  But in fact I haven't corrected for it much at all; I've just assumed that I did so.

299 | 300 |
301 | 302 | 303 |
304 | 305 | She has joined the Conspiracy 306 | 307 | 308 | tag:typepad.com,2003:post-61260822 309 | 2009-01-13T14:48:22-05:00 310 | 2009-01-15T18:05:38-05:00 311 | I have no idea whether I had anything to do with this. 312 | 313 | Eliezer Yudkowsky 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 |

Kimiko

I have no idea whether I had anything to do with this.

322 |
323 | 324 | 325 |
326 | 327 | Building Weirdtopia 328 | 329 | 330 | tag:typepad.com,2003:post-61236322 331 | 2009-01-12T15:35:27-05:00 332 | 2009-01-16T18:22:49-05:00 333 | Followup to: Eutopia is Scary"Two roads diverged in the woods. I took the one less traveled, and had to eat bugs until Park rangers rescued me." -- Jim Rosenberg Utopia and Dystopia have something in common: they both confirm the... 334 | 335 | Eliezer Yudkowsky 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 |

Followup toEutopia is Scary

"Two roads diverged in the woods.  I took the one less traveled, and had to eat bugs until Park rangers rescued me."
        -- Jim Rosenberg 344 | 345 |

Utopia and Dystopia have something in common: they both confirm the moral sensibilities you started with.  Whether the world is a libertarian utopia of the non-initiation of violence and everyone free to start their own business, or a hellish dystopia of government regulation and intrusion - you might like to find yourself in the first, and hate to find yourself in the second; but either way you nod and say, "Guess I was right all along."
346 | 347 |

So as an exercise in creativity, try writing them down side by 348 | side:  Utopia, Dystopia, and Weirdtopia.  The zig, the zag and the zog.

I'll start off with a worked example for public understanding of science:

    349 | 350 |
  • Utopia:  Most people have the equivalent of an undergrad degree in something; everyone reads the popular science books (and they're good books); everyone over the age of nine understands evolutionary theory and Newtonian physics; scientists who make major contributions are publicly adulated like rock stars.
  • 351 |
  • Dystopia:  Science is considered boring and possibly treasonous; public discourse elevates religion or crackpot theories; stem cell research is banned.
  • 352 |
  • Weirdtopia:  Science is kept secret to avoid spoiling the surprises; no public discussion but intense private pursuit; cooperative ventures surrounded by fearsome initiation rituals because that's what it takes for people to feel like they've actually learned a Secret of the Universe and be satisfied; someone you meet may only know extremely basic science, but they'll have personally done revolutionary-level work in it, just like you.  Too bad you can't compare notes.
  • 353 | 354 |
355 | 356 |

Disclaimer 1:  Not every sensibility we have is necessarily wrong.  Originality is a goal of literature, not science; sometimes it's better to be right than to be new.  But there are also such things as cached thoughts.  At least in my own case, it turned out that trying to invent a world that went outside my pre-existing sensibilities, did me a world of good.

Disclaimer 2:  This method is not universal:  Not all interesting ideas fit this mold, and not all ideas that fit this mold are good ones.  Still, it seems like an interesting technique.

If you're trying to write science fiction (where originality is a legitimate goal), then you can write down anything nonobvious for Weirdtopia, and you're done.

If you're trying to do Fun Theory, you have to come up with a Weirdtopia that's at least arguably-better than Utopia.  This is harder but also directs you to more interesting regions of the answer space.

If you can make all your answers coherent with each other, you'll have quite a story setting on your hands.  (Hope you know how to handle characterization, dialogue, description, conflict, and all that other stuff.)

Here's some partially completed challenges, where I wrote down a Utopia and a Dystopia (according to the moral sensibilities I started with before I did this exercise), but inventing a (better) Weirdtopia is left to the reader.

Economic...

    357 | 358 |
  • Utopia:  The world is flat and ultra-efficient.  Prices fall as standards of living rise, thanks to economies of scale.  Anyone can easily start their own business and most people do.  Everything is done in the right place by the right person under Ricardo's Law of Comparative Advantage.  Shocks are efficiently absorbed by the risk capital that insured them.
  • 359 |
  • Dystopia:  Lots of trade barriers and subsidies; corporations exploit the regulatory systems to create new barriers to entry; dysfunctional financial systems with poor incentives and lots of unproductive investments; rampant agent failures and systemic vulnerabilities; standards of living flat or dropping.
  • 360 |
  • Weirdtopia: _____
  • 361 | 362 |
363 |

Sexual...

    364 |
  • Utopia:  Sexual mores straight out of a Spider Robinson novel:  Sexual jealousy has been eliminated; no one is embarrassed about what turns them on; universal tolerance and respect; everyone is bisexual, poly, and a switch; total equality between the sexes; no one would look askance on sex in public any more than eating in public, so long as the participants cleaned up after themselves.
  • 365 |
  • Dystopia:  10% of women have never had an orgasm.  States adopt laws to ban gay marriage.  Prostitution illegal.
  • 366 | 367 |
  • Weirdtopia: _____
  • 368 |
369 |

Governmental...

    370 |
  • Utopia:  Non-initiation of violence is the chief rule. Remaining public issues are settled by democracy:  Well reasoned public debate in which all sides get a free voice, followed by direct or representative majority vote.  Smoothly interfunctioning Privately Produced Law, which coordinate to enforce a very few global rules like "no slavery".
  • 371 | 372 |
  • Dystopia:  Tyranny of a single individual or oligarchy.  Politicians with effective locks on power thanks to corrupted electronic voting systems, voter intimidation, voting systems designed to create coordination problems.  Business of government is unpleasant and not very competitive; hard to move from one region to another.
  • 373 |
  • Weirdtopia: _____
  • 374 |
375 |

Technological...

    376 | 377 |
  • Utopia:  All Kurzweilian prophecies come true simultaneously.  Every pot contains a chicken, a nanomedical package, a personal spaceship, a superdupercomputer, amazing video games, and a pet AI to help you use it all, plus a pony.  Everything is designed by Apple.
  • 378 |
  • Dystopia:  Those damned fools in the government banned everything more complicated than a lawnmower, and we couldn't use our lawnmowers after Peak Oil hit.
  • 379 |
  • Weirdtopia:  _____
  • 380 | 381 |
382 |

Cognitive...

    383 |
  • Utopia:  Brain-computer implants for everyone!  You can do whatever you like with them, it's all voluntary and the dangerous buttons are clearly labeled.  There are AIs around that are way more powerful than you; but they don't hurt you unless you ask to be hurt, sign an informed consent release form and click "Yes" three times.
  • 384 | 385 |
  • Dystopia:  The first self-improving AI was poorly designed, everyone's dead and the universe is being turned into paperclips.  Or the augmented humans hate the normals.  Or augmentations make you go nuts.  Or the darned government banned everything again, and people are still getting Alzheimers due to lack of stem-cell research.
  • 386 |
  • Weirdtopia:  _____
  • 387 |
388 |

389 | 390 |
391 | 392 | 393 |
394 | 395 |
396 | 397 | 398 | -------------------------------------------------------------------------------- /tests/feeds/atom1.0-1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Feed 5 | 6 | 2003-12-13T18:30:02Z 7 | 8 | John Doe 9 | 10 | urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 11 | 12 | 13 | The Exciting conclusion of Atom-Powered Robots Run Amok 14 | 15 | urn:uuid:1225c695-cfb8-4ebb-whatever 16 | 2003-12-13T18:30:02Z 17 | Some text. 18 | 19 | 20 | Atom-Powered Robots Run Amok, part One 21 | 22 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 23 | 2002-10-01T18:30:00Z 24 | arr, texty 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/feeds/rss-userland-dawkins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RichardDawkins.net : The Latest Updates 5 | http://richarddawkins.net/ 6 | The homepage for The Richard Dawkins Foundation for Reason and Science. 7 | en-us 8 | http://backend.userland.com/rss 9 | 10 | PHP/4.4.5 11 | 12 | New Scientist flips the bird at scientists, again 13 | PZ Myers, Jerry Coyne 14 | http://richarddawkins.net/article,3667,n,n 15 | New Scientist continues to mislead the public by using their "Darwin was wrong" cover in promotions. 16 | 17 | 18 | 19 | 20 | 21 | Evolving glory of the Galapagos 22 | The Independent 23 | http://richarddawkins.net/article,3666,n,n 24 | As an exhibition of his wife&#39;s paintings of the islands opens, Richard Dawkins celebrates the Darwinian paradise. 25 | <br /> 26 | 27 | 28 | 29 | 30 | 31 | The deification of stupidity 32 | AC Grayling 33 | http://richarddawkins.net/article,3665,n,n 34 | If the OIC succeeds in turning criticism of religion into &#39;defamation&#39;, freedom of expression will be eradicated 35 | 36 | 37 | 38 | 39 | 40 | Oklahoma Legislature Investigates Richard Dawkins&#39; Free Speech 41 | Greg Lukianoff 42 | http://richarddawkins.net/article,3664,n,n 43 | Well, it&#39;s official: Oklahoma&#39;s state legislature is investigating the University of Oklahoma for hosting a speech by evolutionary biologist Richard Dawkins. 44 | 45 | 46 | 47 | 48 | 49 | An interview with Jerry Coyne 50 | Greg Ross, American Scientist 51 | http://richarddawkins.net/article,3663,n,n 52 | American Scientist Online managing editor Greg Ross interviewed Coyne by telephone in January 2009. 53 | 54 | 55 | 56 | 57 | 58 | The New Atheist Movement is destructive 59 | Julian Baggini 60 | http://richarddawkins.net/article,3662,n,n 61 | The antitheism of the four horsemen is for me a backwards step. It reinforces what I believe is a myth, that an atheist without a bishop to bash is like a fish without water, Julian Baggini writes. 62 | 63 | 64 | 65 | 66 | 67 | France Condemns Pope&#39;s Comments on Condom Use in Africa 68 | VOA News 69 | http://richarddawkins.net/article,3661,n,n 70 | France is criticizing comments by Pope Benedict that condom use is aggravating the AIDS problem in Africa. 71 | 72 | 73 | 74 | 75 | 76 | Free speech is sacred 77 | Pat Condell 78 | http://richarddawkins.net/article,3660,n,n 79 | Time to put a crescent moon on the UN flag? 80 | 81 | 82 | 83 | 84 | Canadian Science minister&#39;s coyness on evolution worries researchers 85 | 86 | CBC 87 | http://richarddawkins.net/article,3659,n,n 88 | Federal Science Minister Gary Goodyear&#39;s refusal to say whether he believes in evolution has left scientists questioning what that means for Canadian research. 89 | 90 | 91 | 92 | 93 | Richard Dawkins&#39; live streaming lecture today 94 | 95 | Open University 96 | http://richarddawkins.net/article,3658,n,n 97 | Richard Dawkins delivers this year&#39;s Open University lecture - and you can watch it live, streamed, from the Natural History Museum, from 7.30pm on Tuesday 17th March 2009. 98 | 99 | 100 | 101 | 102 | There&#39;s a new power in America - atheism 103 | 104 | Andrew Sullivan 105 | http://richarddawkins.net/article,3657,n,n 106 | The faithless are a growing force as the churches duck the challenges of the age 107 | 108 | 109 | 110 | 111 | Is Oklahoma Investigating Richard Dawkins&#39; Free Speech? 112 | 113 | Greg Lukianoff 114 | http://richarddawkins.net/article,3656,n,n 115 | In a case that harkens back to the old-timey censorship of yesteryear, it appears that the Oklahoma legislature is pulling out the stops to oppose the University of Oklahoma&#39;s decision to host the Oxford evolutionary biologist Richard Dawkins on its campus. 116 | <br /> 117 | 118 | 119 | 120 | 121 | Deborah 13: Servant of God 122 | 123 | BBC 3 124 | http://richarddawkins.net/article,3655,n,n 125 | She has been brought up in a deeply Christian family and her parents have tried to make sure she and her ten brothers and sisters have grown up protected from the sins of the outside world. 126 | 127 | 128 | 129 | 130 | Student facing 20 years in hell 131 | Jerome Stakey 132 | 133 | http://richarddawkins.net/article,3654,n,n 134 | Afghan court secretly sentences student whose cause was taken up by The Independent. His crime? To download article on women&#39;s rights 135 | 136 | 137 | 138 | 139 | Anti-atheist prejudice widespread in America 140 | Zac Smith 141 | 142 | http://richarddawkins.net/article,3653,n,n 143 | Americans find atheists a particularly repugnant minority. According to Gallup, they are more disliked than any other major religious group, with the exception of Scientologists. 144 | 145 | 146 | 147 | 148 | The coming evangelical collapse 149 | The Christian Science Monitor, Michael Spencer 150 | http://richarddawkins.net/article,3652,n,n 151 | 152 | An anti-Christian chapter in Western history is about to begin. But out of the ruins, a new vitality and integrity will rise. 153 | 154 | 155 | 156 | 157 | State representative disapproves of Darwin 2009 Project 158 | Cadie Thompson 159 | http://richarddawkins.net/article,3651,n,n 160 | foo 161 | 162 | 163 | 164 | 165 | 166 | Belief and the brain&#39;s &#39;God spot&#39; 167 | The Independent 168 | http://richarddawkins.net/article,3650,n,n 169 | Scientists say they have located the parts of the brain that control religious faith. And the research proves, they contend, that belief in a higher power is an evolutionary asset that helps human survival. Steve Connor reports 170 | 171 | 172 | 173 | 174 | 175 | Vatican says Evolution does not prove the non-existence of God 176 | Times Online 177 | http://richarddawkins.net/article,3649,n,n 178 | Evolution and the Biblical account of Genesis are "perfectly compatible" claims the Catholic Church 179 | 180 | 181 | 182 | 183 | 184 | The Elfish Gene 185 | Mark Barrowcliffe 186 | http://richarddawkins.net/article,3648,n,n 187 | A different kind of flea! 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /tests/xml/simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | here's text 5 | yep! 6 | 7 | more text 8 | 9 | 10 | i am another foo 11 | 12 | 13 | delicious! 14 | 15 15 | crunchy, too! 16 | 17 | 18 | i am YET ANOTHER foo!....! 19 | 20 | 21 | --------------------------------------------------------------------------------