From 47fe4fc4fc11755568282889a875d5733b79418c Mon Sep 17 00:00:00 2001 From: Odin Kroeger Date: Wed, 9 May 2018 18:14:46 +0200 Subject: [PATCH] First commit. --- Makefile | 26 + README.rst | 135 ++++ .../rocks-5.3/lunajson/1.2-0/doc/CHANGELOG.md | 8 + .../rocks-5.3/lunajson/1.2-0/doc/LICENSE | 21 + .../rocks-5.3/lunajson/1.2-0/doc/README.md | 84 ++ .../lunajson/1.2-0/lunajson-1.2-0.rockspec | 31 + .../rocks-5.3/lunajson/1.2-0/rock_manifest | 16 + lib/luarocks/rocks-5.3/manifest | 49 ++ man/pandoc-zotxt.lua.1 | 85 +++ man/pandoc-zotxt.lua.rst | 72 ++ pandoc-zotxt.lua | 175 +++++ share/lua/5.3/lunajson.lua | 11 + share/lua/5.3/lunajson/decoder.lua | 518 +++++++++++++ share/lua/5.3/lunajson/encoder.lua | 185 +++++ share/lua/5.3/lunajson/sax.lua | 718 ++++++++++++++++++ test/call-citeproc-is.html | 30 + test/call-citeproc-should.html | 30 + test/call-citeproc.md | 29 + test/doc-is.html | 30 + test/doc-should.html | 30 + test/doc.md | 25 + test/dont-call-citeproc-is.html | 13 + test/dont-call-citeproc-should.html | 13 + test/dont-call-citeproc.md | 29 + test/items.rdf | 210 +++++ test/long.md | 121 +++ 26 files changed, 2694 insertions(+) create mode 100644 Makefile create mode 100644 README.rst create mode 100644 lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/CHANGELOG.md create mode 100644 lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/LICENSE create mode 100644 lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/README.md create mode 100644 lib/luarocks/rocks-5.3/lunajson/1.2-0/lunajson-1.2-0.rockspec create mode 100644 lib/luarocks/rocks-5.3/lunajson/1.2-0/rock_manifest create mode 100644 lib/luarocks/rocks-5.3/manifest create mode 100644 man/pandoc-zotxt.lua.1 create mode 100644 man/pandoc-zotxt.lua.rst create mode 100644 pandoc-zotxt.lua create mode 100644 share/lua/5.3/lunajson.lua create mode 100644 share/lua/5.3/lunajson/decoder.lua create mode 100644 share/lua/5.3/lunajson/encoder.lua create mode 100644 share/lua/5.3/lunajson/sax.lua create mode 100644 test/call-citeproc-is.html create mode 100644 test/call-citeproc-should.html create mode 100644 test/call-citeproc.md create mode 100644 test/doc-is.html create mode 100644 test/doc-should.html create mode 100644 test/doc.md create mode 100644 test/dont-call-citeproc-is.html create mode 100644 test/dont-call-citeproc-should.html create mode 100644 test/dont-call-citeproc.md create mode 100644 test/items.rdf create mode 100644 test/long.md diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..624189f --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +test: test-doc test-call-citeproc test-dont-call-citeproc + +test-doc: + rm -f test/doc-is.html + pandoc --lua-filter ./pandoc-zotxt.lua -F pandoc-citeproc \ + -o test/doc-is.html test/doc.md + cmp test/doc-is.html test/doc-should.html + +test-call-citeproc: + rm -f test/call-citeproc-is.html + pandoc --lua-filter ./pandoc-zotxt.lua \ + -o test/call-citeproc-is.html test/call-citeproc.md + cmp test/call-citeproc-is.html test/call-citeproc-should.html + +test-dont-call-citeproc: + rm -f test/dont-call-citeproc-is.html + pandoc --lua-filter ./pandoc-zotxt.lua \ + -o test/dont-call-citeproc-is.html test/dont-call-citeproc.md + cmp test/dont-call-citeproc-is.html test/dont-call-citeproc-should.html + +performance-comparison: + time pandoc -F pandoc-zotxt -o /dev/null test/long.md + time pandoc --lua-filter ./pandoc-zotxt.lua -o /dev/null test/long.md + +.PHONY: test test-doc test-call-citeproc test-dont-call-citeproc \ + performance-comparison diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a6da552 --- /dev/null +++ b/README.rst @@ -0,0 +1,135 @@ +================ +pandoc-zotxt.lua +================ + +``pandoc-zotxt.lua`` looks up sources of citations in Zotero and adds +their bibliographic data to the metadata of the document, where it +can be read by ``pandoc-citeproc``. + +You need the zotxt_ plugin for Zotero. Citations should be inserted +as so-called easy citekeys. See the documentation of *zotxt* for details. + +See the `manual page `_ for more details. + + + +Installing ``pandoc-zotxt.lua`` +=============================== + +You use ``pandoc-zotxt.lua`` **at your own risk**. You have been warned. + +You need Pandoc_ 2.0 or later. If you are using an older version of Pandoc, +try `pandoc-zotxt `_, +which works with Pandoc 1.12 or later (but also requires Python_ 2.7). + +1. Download the `current release + `_. +2. Unpack it. +3. Copy the whole directory to the ``filters`` + subdirectory of your Pandoc data directory. + +Where your Pandoc data directory is located depends on your operating system. +``pandoc --version`` will tell you. Consult the Pandoc manual for details. + +You may also want to copy the manual page to wherever your system stores manual +pages; typically, this is ``/usr/local/share/man/``. + +If you are using a Unix-ish operating system, you can do all of the above by:: + + PANDOC_DATA_DIR=$(pandoc --version | + sed -n 's/^Default user data directory: //p') + mkdir -p "${PANDOC_DATA_DIR:?}/filters" + cd "${PANDOC_DATA_DIR:?}/filters" + curl https://codeload.github.com/odkr/pandoc-zotxt.lua/tar.gz/v0.1 | + tar -xz + sudo cp pandoc-zotxt.lua-0.1/man/pandoc-zotxt.lua.1 \ + /usr/local/share/man/man1 + + +``pandoc-zotxt.lua`` vs ``pandoc-zotxt`` +======================================== + +I started to write ``pandoc-zotxt.lua`` because I had hoped that I could write +a faster replacement for ``pandoc-zotxt``. Unfortunately, Pandoc_ does *not* +support LuaSocket_ (a library for, among other things, retrieving data via a +network) and only provides a blocking method to fetch data from networks +itself. So, there is no way to retrieve data for multiple citation items +concurrently. As a consequence, ``pandoc-zotxt.lua`` is about as fast as +``pandoc-zotxt``. + ++------------------------------------+---------------------------------------+ +| ``pandoc-zotxt.lua`` | ``pandoc-zotxt`` | ++====================================+=======================================+ +| Requires only Pandoc_ 2.0 | Requires Pandoc_ 1.12 and Python_ 2.7 | ++------------------------------------+---------------------------------------+ +| Apparently, a tiny bit faster | Apparently, a tiny bit faster | +| for long and/or complex documents. | for short and/or simple documents. | +| (But you won't notice.) | (But you won't notice.) | ++------------------------------------+---------------------------------------+ + + +Test suite +========== + +For the test suite to work, you need Zotero_ and the sources that are cited +in the test documents. You can import those sources from the file ``items.rdf`` +in the directory ``test``. To run the test suite, just say:: + + make test + + +Documentation +============= + +See the `manual page `_ +and the source for details. + + +Contact +======= + +If there's something wrong with ``pandoc-zotxt.lua``, `open an issue +`_. + + +License +======= + +Copyright 2018 Odin Kroeger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Further Information +=================== + +GitHub: + + + +See also +======== + + + +.. _zotxt: https://github.com/egh/zotxt +.. _Zotero: https://www.zotero.org/ +.. _Pandoc: https://www.pandoc.org/ +.. _Python: https://www.python.org/ +.. _LuaSocket: https://github.com/diegonehab/luasocket diff --git a/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/CHANGELOG.md b/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/CHANGELOG.md new file mode 100644 index 0000000..e135508 --- /dev/null +++ b/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/CHANGELOG.md @@ -0,0 +1,8 @@ +# 1.2.0 + +* For ease of embedded use, the decoder and the SAX parser are made self-contained one file implementation. +* `1` is decoded as an integer on Lua 5.3. +* Number parsing routine is changed. Now `-?[0-9][-+.A-Za-z0-9]*` is detected as a number, and its conformance to JSON spec is checked. +* Automated testing. +* Cool logo :) +* Bug fixes. diff --git a/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/LICENSE b/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/LICENSE new file mode 100644 index 0000000..6454737 --- /dev/null +++ b/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2017 Shunsuke Shimizu (grafi) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/README.md b/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/README.md new file mode 100644 index 0000000..2ef0588 --- /dev/null +++ b/lib/luarocks/rocks-5.3/lunajson/1.2-0/doc/README.md @@ -0,0 +1,84 @@ +# ![Lunajson](logo/lunajson.png) +[![CircleCI](https://circleci.com/gh/grafi-tt/lunajson.svg?style=shield)](https://circleci.com/gh/grafi-tt/lunajson) + +Lunajson features SAX-style JSON parser and simple JSON decoder/encoder. It is tested on Lua 5.1, Lua 5.2, Lua 5.3, and LuaJIT 2.0. +It is written only in pure Lua and has no dependencies. Even so, decoding speed matches lpeg-based JSON implementations because it is carefully optimized. +The parser and decoder reject input that is not conformant to the JSON specification (ECMA-404), and the encoder always yields conformant output. +The parser and decoder also handle UTF/Unicode surrogate pairs correctly. + +## Install + luarocks install lunajson + +Or you can download source manually and copy `src/*` into somewhere on your `package.path`. + +## Simple Usage + local lunajson = require 'lunajson' + local jsonstr = '{"Hello":["lunajson",1.5]}' + local t = lunajson.decode(jsonstr) + print(t.Hello[2]) -- prints 1.5 + print(lunajson.encode(t)) -- prints {"Hello":["lunajson",1.5]} + +## API +### lunajson.decode(jsonstr, [pos, [nullv, [arraylen]]]) +Decode `jsonstr`. If `pos` is specified, it starts decoding from `pos` until the JSON definition ends, otherwise the entire input is parsed as JSON. `null` inside `jsonstr` will be decoded as the optional sentinel value `nullv` if specified, and discarded otherwise. If `arraylen` is true, the length of an array `ary` will be stored in `ary[0]`. This behavior is useful when empty arrays should not be confused with empty objects. + +This function returns the decoded value if `jsonstr` contains valid JSON, otherwise an error will be raised. If `pos` is specified it also returns the position immediately after the end of decoded JSON. + +### lunajson.encode(value, [nullv]) +Encode `value` into a JSON string and return it. If `nullv` is specified, values equal to `nullv` will be encoded as `null`. + +This function encodes a table `t` as a JSON array if a value `t[1]` is present or a number `t[0]` is present. If `t[0]` is present, its value is considered as the length of the array. Then the array may contain `nil` and those will be encoded as `null`. Otherwise, this function scans non `nil` values starting from index 1, up to the first `nil` it finds. When the table `t` is not an array, it is an object and all of its keys must be strings. + +### lunajson.newparser(input, saxtbl) +### lunajson.newfileparser(filename, saxtbl) +Create and return a sax-style parser context, which parses `input` or a file specified by `filename`. `input` can be a string to be parsed, or a function that returns the next chunk of a data as a string to be parsed (or `nil` when all data is yielded). An example function for `input` follows (this sample is essentially same as the implementation of `newfileparser`). Note that `input` will never be called once it has returned `nil`. + + local fp = io.open("myfavorite.json") + local function input() + local s + if fp then + s = fp:read(8192) + if not s then + fp:close() + fp = nil + end + end + return s + end + +`saxtbl` is a table of callbacks. It can have the following functions. Those functions will be called on corresponding events, if it is in the table. + +- startobject() +- key(s) +- endobject() +- startarray() +- endarray() +- string(s) +- number(n) +- boolean(b) +- null() + +A parser context maintains the current parse position, initially 1. + +#### parsercontext.run() +Start parsing from current position. If valid JSON is parsed, the position moves to just after the end of this JSON. Otherwise it errors. + +#### parsercontext.tellpos() +Return current position. + +#### parsercontext.tryc() +Return the byte of current position as a number. If input is ended, it returns `nil`. It does not change current position. + +#### parsercontext.read(n) +Return the `n`-length string starting from current position, and increase the index by `n`. If the input ends, the returned string and the updated position will be truncated. + +## Benchmark +Following graphs are the results of the benchmark, decoding [`simple.json`](test/decodeparse/benchjson/simple.json) (about 750KiB) 100 times and encoding [`simple.lua`](test/encode/benchdata/simple.lua) (the decoded result of `simple.json`) 100 times. I conducted benchmarks of lunajson 1.0, [dkjson 2.5](http://dkolf.de/src/dkjson-lua.fsl/home) and [Lua CJSON 2.1.0](http://www.kyne.com.au/~mark/software/lua-cjson.php). Dkjson is a popular JSON encoding/decoding library in Lua, which is written in Lua and optionally uses [lpeg](http://www.inf.puc-rio.br/~roberto/lpeg/) to spped up decoding. Lua CJSON is a JSON encoding/decoding library implemented in C and is inherently fast. + +![The graph of decoding benchmark results](result/decode-simple.png) + +![The graph of encoding benchmark results](result/encode-simple.png) + +This benchmark is conducted in my desktop machine that equips Core i5 3550K and DDR3-1600 memory. Lua implementations and concerning modules are compiled by GCC 4.9.2 with `-O2 -march=ivybridge -mtune=ivybridge` options. The versions of lua implementations are the newest official releases at the time of benchmark. The version of lpeg is 0.12.2. + +In this benchmark Lunajson performs well considering that it is implemented only in standard Lua, especially in LuaJIT 2.0 benchmark. Lunajson also supplies incremental parsing in a SAX-style API, therfore you don't have to load whole large JSON files into memory in order to scan the information you're interested in from them. Lunajson is especially useful when non-standard libraries cannot be used easily or incremental parsing is favored. diff --git a/lib/luarocks/rocks-5.3/lunajson/1.2-0/lunajson-1.2-0.rockspec b/lib/luarocks/rocks-5.3/lunajson/1.2-0/lunajson-1.2-0.rockspec new file mode 100644 index 0000000..7608954 --- /dev/null +++ b/lib/luarocks/rocks-5.3/lunajson/1.2-0/lunajson-1.2-0.rockspec @@ -0,0 +1,31 @@ +package = "lunajson" +version = "1.2-0" +source = { + url = "git://github.com/grafi-tt/lunajson.git", + tag = "1.2" +} +description = { + summary = "A strict and fast JSON parser/decoder/encoder written in pure Lua", + detailed = [[ + Lunajson features SAX-style JSON parser and simple JSON decoder/encoder. It is tested on Lua 5.1, Lua 5.2, Lua 5.3, and LuaJIT. + It is written only in pure Lua and has no dependencies. Even though, since it is carefully optimized, decoding speed even matches to other lpeg-based JSON modules. + The parser and decoder reject inputs not conforms the JSON specification (ECMA-404), and the encoder always yields outputs conforming the specification. + The parser and decoder also handle surrogate pair correctly. + ]], + homepage = "https://github.com/grafi-tt/lunajson", + maintainer = "Shunsuke Shimizu", + license = "MIT/X11" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = 'builtin', + modules = { + ['lunajson'] = 'src/lunajson.lua', + + ['lunajson.decoder'] = 'src/lunajson/decoder.lua', + ['lunajson.encoder'] = 'src/lunajson/encoder.lua', + ['lunajson.sax' ] = 'src/lunajson/sax.lua', + } +} diff --git a/lib/luarocks/rocks-5.3/lunajson/1.2-0/rock_manifest b/lib/luarocks/rocks-5.3/lunajson/1.2-0/rock_manifest new file mode 100644 index 0000000..0529957 --- /dev/null +++ b/lib/luarocks/rocks-5.3/lunajson/1.2-0/rock_manifest @@ -0,0 +1,16 @@ +rock_manifest = { + doc = { + ["CHANGELOG.md"] = "b173b6220589bd7e512cb5ea1f009060", + LICENSE = "2001d13200cac722635f81768f80463e", + ["README.md"] = "c6d5d3393e5d54b40abd764d1eb24dcb" + }, + lua = { + lunajson = { + ["decoder.lua"] = "a02440404a53a1c6d7cd70c9362884f4", + ["encoder.lua"] = "197d6edcbc03cea7db3d918493c11616", + ["sax.lua"] = "bf58ae0c4a877ba4e49ee31b05a14fed" + }, + ["lunajson.lua"] = "64bce3b8bcfa53c02e64fd924c1cf4b4" + }, + ["lunajson-1.2-0.rockspec"] = "e16bb1378fa36d6a662964ea925efa48" +} diff --git a/lib/luarocks/rocks-5.3/manifest b/lib/luarocks/rocks-5.3/manifest new file mode 100644 index 0000000..219d8e0 --- /dev/null +++ b/lib/luarocks/rocks-5.3/manifest @@ -0,0 +1,49 @@ +commands = {} +dependencies = { + lunajson = { + ["1.2-0"] = { + { + constraints = { + { + op = ">=", + version = { + 5, 1, string = "5.1" + } + } + }, + name = "lua" + } + } + } +} +modules = { + lunajson = { + "lunajson/1.2-0" + }, + ["lunajson.decoder"] = { + "lunajson/1.2-0" + }, + ["lunajson.encoder"] = { + "lunajson/1.2-0" + }, + ["lunajson.sax"] = { + "lunajson/1.2-0" + } +} +repository = { + lunajson = { + ["1.2-0"] = { + { + arch = "installed", + commands = {}, + dependencies = {}, + modules = { + lunajson = "lunajson.lua", + ["lunajson.decoder"] = "lunajson/decoder.lua", + ["lunajson.encoder"] = "lunajson/encoder.lua", + ["lunajson.sax"] = "lunajson/sax.lua" + } + } + } + } +} diff --git a/man/pandoc-zotxt.lua.1 b/man/pandoc-zotxt.lua.1 new file mode 100644 index 0000000..8962cba --- /dev/null +++ b/man/pandoc-zotxt.lua.1 @@ -0,0 +1,85 @@ +.\" Man page generated from reStructuredText. +. +.TH PANDOC-ZOTXT.LUA 1 "May 9, 2018" "0.1" "" +.SH NAME +pandoc-zotxt.lua \- Looks up sources in Zotero +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH SYNOPSIS +.sp +pandoc [...] \-\-lua\-filter pandoc\-zotxt.lua\-0.1/pandoc\-zotxt.lua [...] +.SH DESCRIPTION +.sp +\fBpandoc\-zotxt.lua\fP looks up sources of citations in Zotero and adds +their bibliographic data to the metadata of the document, where it +can be read by \fBpandoc\-citeproc\fP\&. +.sp +You need the \fBzotxt\fP plugin for Zotero. Citations should be inserted +as so\-called easy citekeys. See the documentation of \fBzotxt\fP for details. +.sp +If you want \fBpandoc\-zotxt.lua\fP to call \fBpandoc\-citeproc\fP automatically, +set the metadata field \fBcall\-citeproc\fP to \fBtrue\fP (or another truey value). +This is useful if you are using \fBpanzer\fP, which insists on calling +Lua filters after \(aqordinary\(aq ones. +.SH LICENSE +.sp +Copyright 2018 Odin Kroeger +.sp +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +.sp +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +.sp +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +.SH FURTHER INFORMATION +.INDENT 0.0 +.IP \(bu 2 +<\fI\%https://github.com/odkr/pandoc\-zotxt.lua\fP> +.IP \(bu 2 +<\fI\%https://github.com/egh/zotxt\fP> +.IP \(bu 2 +<\fI\%https://github.com/msprev/panzer\fP> +.UNINDENT +.SH SEE ALSO +.sp +pandoc(1), pandoc\-citeproc(1) +.SH AUTHOR +Odin Kroeger +.\" Generated by docutils manpage writer. +. diff --git a/man/pandoc-zotxt.lua.rst b/man/pandoc-zotxt.lua.rst new file mode 100644 index 0000000..b3cb25e --- /dev/null +++ b/man/pandoc-zotxt.lua.rst @@ -0,0 +1,72 @@ +================ +pandoc-zotxt.lua +================ + +-------------------------- +Looks up sources in Zotero +-------------------------- + +:Author: Odin Kroeger +:Date: May 9, 2018 +:Version: 0.1 +:Manual section: 1 + + +SYNOPSIS +======== + +pandoc [...] --lua-filter pandoc-zotxt.lua-0.1/pandoc-zotxt.lua [...] + + +DESCRIPTION +=========== + +``pandoc-zotxt.lua`` looks up sources of citations in Zotero and adds +their bibliographic data to the metadata of the document, where it +can be read by ``pandoc-citeproc``. + +You need the ``zotxt`` plugin for Zotero. Citations should be inserted +as so-called easy citekeys. See the documentation of ``zotxt`` for details. + +If you want ``pandoc-zotxt.lua`` to call ``pandoc-citeproc`` automatically, +set the metadata field ``call-citeproc`` to ``true`` (or another truey value). +This is useful if you are using ``panzer``, which insists on calling +Lua filters after 'ordinary' ones. + + +LICENSE +======= + +Copyright 2018 Odin Kroeger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +FURTHER INFORMATION +=================== + +* +* +* + + +SEE ALSO +======== + +pandoc(1), pandoc-citeproc(1) diff --git a/pandoc-zotxt.lua b/pandoc-zotxt.lua new file mode 100644 index 0000000..19092ff --- /dev/null +++ b/pandoc-zotxt.lua @@ -0,0 +1,175 @@ +#!/usr/local/bin/lua +--- pandoc-zotxt.lua Looks up citations in Zotero and adds references. +-- +-- @release 0.1 +-- @author Odin Kroeger +-- @copyright 2018 Odin Kroeger +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to +-- deal in the Software without restriction, including without limitation the +-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +-- sell copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +-- IN THE SOFTWARE. + +-- Constants +-- ========= + +-- The URL to lookup citation data. +-- See for details. +local ZOTERO_LOOKUP_URL = 'http://127.0.0.1:23119/zotxt/items?easykey=' + + +-- Boilerplate +-- =========== + +local package = package +local text = require 'text' + +do + local s_dir = string.match(PANDOC_SCRIPT_FILE, '(.-)[\\/][^\\/]-$') or '.' + local path_sep = package.config:sub(1, 1) + local lua_vers = {} + for _, v in ipairs({_VERSION:sub(5, 7), '5.3'}) do lua_vers[v] = true end + for k, _ in pairs(lua_vers) do + package.path = package.path .. ';' .. + table.concat({s_dir, 'share', 'lua', k, '?.lua'}, path_sep) + package.cpath = package.cpath .. ';' .. + table.concat({s_dir, 'lib', 'lua', k, '?.so'}, path_sep) + end +end + +-- C-JSON is slightly faster; just in case you're writing a book. +local json +do + local cjson + cjson, json = pcall(function() return require 'cjson' end) + if cjson then + json.encode_number_precision(1) + else + json = require 'lunajson' + end +end + + +-- Functions +-- ========= + +--- Gets bibliographic data from Zotero. +-- +-- Uses the constant ZOTERO_LOOKUP_URL (see above). +-- See for details. +-- +-- @param citekey A citation key. +-- +-- @return If the cited source was found, bibliographic data for +-- that source as CSL JSON string. +-- @return Otherwise, nil and and error message. +function get_source_data (citekey) + local _, reply = pandoc.mediabag.fetch(ZOTERO_LOOKUP_URL .. citekey, '.') + if reply:sub(1, 1) ~= '[' then return nil, reply end + return reply +end + + +--- Converts all numbers in a multi-dimensional table to strings. +-- +-- Also converts floating point numbers to integers. +-- This is needed because in JavaScript, all numbers are +-- floating point numbers. But Pandoc expects integers. +-- +-- @parem data Data of any type. +-- +-- @return The given data, with all numbers converted into strings. +function stringify_values (data) + local data_type = type(data) + if data_type == 'table' then + local s = {} + for k, v in pairs(data) do s[k] = stringify_values(v) end + return s + elseif data_type == 'number' then + return tostring(math.floor(data)) + else + return data + end +end + + +do + local citekeys = {} + + --- Collects all citekeys used in a document. + -- + -- Saves them into the variable ``citekeys``, + -- which is shared with ``add_references``. + -- + -- @param citations A pandoc.Cite element. + function collect_sources (citations) + for _, citation in ipairs(citations.c[1]) do + if citekeys[citation.id] == nil then + citekeys[citation.id] = true + end + end + end + + + --- Adds all cited sources to the metadata block of a document. + -- + -- Reads citekeys of cited sources from the variable ```citekeys``, + -- which is shared with ``collect_sources``. + -- + -- @param meta The metadata block of a document, as Pandoc.Meta. + -- + -- @return If sources were found, an updated metadata block, + -- as Pandoc.Meta, with the field ```references`` added. + -- @return Otherwise, nil. + -- + -- Prints error messages to STDERR if a source cannot be found. + function add_references (meta) + local sources = {} + for citekey, _ in pairs(citekeys) do + local data, msg = get_source_data(citekey) + if data == nil then + io.stderr:write('pandoc-zotxt.lua: ' .. msg .. '\n') + else + source = stringify_values(json.decode(data)[1]) + source.id = citekey + table.insert(sources, source) + end + end + if #sources > 0 then + meta['references'] = sources + return meta + end + end +end + + +--- Calls citeproc, if requested. +-- +-- If the metadata field ``call-citeproc`` is set to true, +-- calls citeproc to process citations. +-- +-- @param doc The document in which to process citations, as pandoc.Pandoc. +-- +-- @return If ``call-citeproc`` is true, the given document, +-- with citations processed, as pandoc.Pandoc. +-- @return Otherwise, nil. +function call_citeproc (doc) + if doc.meta['call-citeproc'] == true then + return pandoc.utils.run_json_filter(doc, 'pandoc-citeproc') + end +end + +return {{Cite = collect_sources}, {Meta = add_references}, {Pandoc = call_citeproc}} diff --git a/share/lua/5.3/lunajson.lua b/share/lua/5.3/lunajson.lua new file mode 100644 index 0000000..9b9bc47 --- /dev/null +++ b/share/lua/5.3/lunajson.lua @@ -0,0 +1,11 @@ +local newdecoder = require 'lunajson.decoder' +local newencoder = require 'lunajson.encoder' +local sax = require 'lunajson.sax' +-- If you need multiple contexts of decoder and/or encoder, +-- you can require lunajson.decoder and/or lunajson.encoder directly. +return { + decode = newdecoder(), + encode = newencoder(), + newparser = sax.newparser, + newfileparser = sax.newfileparser, +} diff --git a/share/lua/5.3/lunajson/decoder.lua b/share/lua/5.3/lunajson/decoder.lua new file mode 100644 index 0000000..4478894 --- /dev/null +++ b/share/lua/5.3/lunajson/decoder.lua @@ -0,0 +1,518 @@ +local setmetatable, tonumber, tostring = + setmetatable, tonumber, tostring +local floor, inf = + math.floor, math.huge +local mininteger, tointeger = + math.mininteger or nil, math.tointeger or nil +local byte, char, find, gsub, match, sub = + string.byte, string.char, string.find, string.gsub, string.match, string.sub + +local function _decode_error(pos, errmsg) + error("parse error at " .. pos .. ": " .. errmsg, 2) +end + +local f_str_ctrl_pat +if _VERSION == "Lua 5.1" then + -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly + f_str_ctrl_pat = '[^\32-\255]' +else + f_str_ctrl_pat = '[\0-\31]' +end + +local _ENV = nil + + +local function newdecoder() + local json, pos, nullv, arraylen, rec_depth + + -- `f` is the temporary for dispatcher[c] and + -- the dummy for the first return value of `find` + local dispatcher, f + + --[[ + Helper + --]] + local function decode_error(errmsg) + return _decode_error(pos, errmsg) + end + + --[[ + Invalid + --]] + local function f_err() + decode_error('invalid value') + end + + --[[ + Constants + --]] + -- null + local function f_nul() + if sub(json, pos, pos+2) == 'ull' then + pos = pos+3 + return nullv + end + decode_error('invalid value') + end + + -- false + local function f_fls() + if sub(json, pos, pos+3) == 'alse' then + pos = pos+4 + return false + end + decode_error('invalid value') + end + + -- true + local function f_tru() + if sub(json, pos, pos+2) == 'rue' then + pos = pos+3 + return true + end + decode_error('invalid value') + end + + --[[ + Numbers + Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp) + is captured as a number and its conformance to the JSON spec is checked. + --]] + -- deal with non-standard locales + local radixmark = match(tostring(0.5), '[^0-9]') + local fixedtonumber = tonumber + if radixmark ~= '.' then + if find(radixmark, '%W') then + radixmark = '%' .. radixmark + end + fixedtonumber = function(s) + return tonumber(gsub(s, '.', radixmark)) + end + end + + local function number_error() + return decode_error('invalid number') + end + + -- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?` + local function f_zro(mns) + local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0 + + if num == '' then + if c == '' then + if mns then + return -0.0 + end + return 0 + end + + if c == 'e' or c == 'E' then + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + if c == '' then + pos = pos + #num + if mns then + return -0.0 + end + return 0.0 + end + end + number_error() + end + + if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then + number_error() + end + + if c ~= '' then + if c == 'e' or c == 'E' then + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + end + if c ~= '' then + number_error() + end + end + + pos = pos + #num + c = fixedtonumber(num) + + if mns then + c = -c + end + return c + end + + -- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?` + local function f_num(mns) + pos = pos-1 + local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos) + if byte(num, -1) == 0x2E then -- error if ended with period + number_error() + end + + if c ~= '' then + if c ~= 'e' and c ~= 'E' then + number_error() + end + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + if not num or c ~= '' then + number_error() + end + end + + pos = pos + #num + c = fixedtonumber(num) + + if mns then + c = -c + if c == mininteger and not find(num, '[^0-9]') then + c = mininteger + end + end + return c + end + + -- skip minus sign + local function f_mns() + local c = byte(json, pos) + if c then + pos = pos+1 + if c > 0x30 then + if c < 0x3A then + return f_num(true) + end + else + if c > 0x2F then + return f_zro(true) + end + end + end + decode_error('invalid number') + end + + --[[ + Strings + --]] + local f_str_hextbl = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, inf, inf, inf, inf, inf, inf, + inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, + } + f_str_hextbl.__index = function() + return inf + end + setmetatable(f_str_hextbl, f_str_hextbl) + + local f_str_escapetbl = { + ['"'] = '"', + ['\\'] = '\\', + ['/'] = '/', + ['b'] = '\b', + ['f'] = '\f', + ['n'] = '\n', + ['r'] = '\r', + ['t'] = '\t', + } + f_str_escapetbl.__index = function() + decode_error("invalid escape sequence") + end + setmetatable(f_str_escapetbl, f_str_escapetbl) + + local function surrogate_first_error() + return decode_error("1st surrogate pair byte not continued by 2nd") + end + + local f_str_surrogate_prev = 0 + local function f_str_subst(ch, ucode) + if ch == 'u' then + local c1, c2, c3, c4, rest = byte(ucode, 1, 5) + ucode = f_str_hextbl[c1-47] * 0x1000 + + f_str_hextbl[c2-47] * 0x100 + + f_str_hextbl[c3-47] * 0x10 + + f_str_hextbl[c4-47] + if ucode ~= inf then + if ucode < 0x80 then -- 1byte + if rest then + return char(ucode, rest) + end + return char(ucode) + elseif ucode < 0x800 then -- 2bytes + c1 = floor(ucode / 0x40) + c2 = ucode - c1 * 0x40 + c1 = c1 + 0xC0 + c2 = c2 + 0x80 + if rest then + return char(c1, c2, rest) + end + return char(c1, c2) + elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes + c1 = floor(ucode / 0x1000) + ucode = ucode - c1 * 0x1000 + c2 = floor(ucode / 0x40) + c3 = ucode - c2 * 0x40 + c1 = c1 + 0xE0 + c2 = c2 + 0x80 + c3 = c3 + 0x80 + if rest then + return char(c1, c2, c3, rest) + end + return char(c1, c2, c3) + elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st + if f_str_surrogate_prev == 0 then + f_str_surrogate_prev = ucode + if not rest then + return '' + end + surrogate_first_error() + end + f_str_surrogate_prev = 0 + surrogate_first_error() + else -- surrogate pair 2nd + if f_str_surrogate_prev ~= 0 then + ucode = 0x10000 + + (f_str_surrogate_prev - 0xD800) * 0x400 + + (ucode - 0xDC00) + f_str_surrogate_prev = 0 + c1 = floor(ucode / 0x40000) + ucode = ucode - c1 * 0x40000 + c2 = floor(ucode / 0x1000) + ucode = ucode - c2 * 0x1000 + c3 = floor(ucode / 0x40) + c4 = ucode - c3 * 0x40 + c1 = c1 + 0xF0 + c2 = c2 + 0x80 + c3 = c3 + 0x80 + c4 = c4 + 0x80 + if rest then + return char(c1, c2, c3, c4, rest) + end + return char(c1, c2, c3, c4) + end + decode_error("2nd surrogate pair byte appeared without 1st") + end + end + decode_error("invalid unicode codepoint literal") + end + if f_str_surrogate_prev ~= 0 then + f_str_surrogate_prev = 0 + surrogate_first_error() + end + return f_str_escapetbl[ch] .. ucode + end + + -- caching interpreted keys for speed + local f_str_keycache = setmetatable({}, {__mode="v"}) + + local function f_str(iskey) + local newpos = pos-2 + local pos2 = pos + local c1, c2 + repeat + newpos = find(json, '"', pos2, true) -- search '"' + if not newpos then + decode_error("unterminated string") + end + pos2 = newpos+1 + while true do -- skip preceding '\\'s + c1, c2 = byte(json, newpos-2, newpos-1) + if c2 ~= 0x5C or c1 ~= 0x5C then + break + end + newpos = newpos-2 + end + until c2 ~= 0x5C -- leave if '"' is not preceded by '\' + + local str = sub(json, pos, pos2-2) + pos = pos2 + + if iskey then -- check key cache + pos2 = f_str_keycache[str] + if pos2 then + return pos2 + end + pos2 = str + end + + if find(str, f_str_ctrl_pat) then + decode_error("unescaped control string") + end + if find(str, '\\', 1, true) then -- check whether a backslash exists + -- We need to grab 4 characters after the escape char, + -- for encoding unicode codepoint to UTF-8. + -- As we need to ensure that every first surrogate pair byte is + -- immediately followed by second one, we grab upto 5 characters and + -- check the last for this purpose. + str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst) + if f_str_surrogate_prev ~= 0 then + f_str_surrogate_prev = 0 + decode_error("1st surrogate pair byte not continued by 2nd") + end + end + if iskey then -- commit key cache + f_str_keycache[pos2] = str + end + return str + end + + --[[ + Arrays, Objects + --]] + -- array + local function f_ary() + rec_depth = rec_depth + 1 + if rec_depth > 1000 then + decode_error('too deeply nested json (> 1000)') + end + local ary = {} + + f, pos = find(json, '^[ \n\r\t]*', pos) + pos = pos+1 + + local i = 0 + if byte(json, pos) ~= 0x5D then -- check closing bracket ']' which means the array empty + local newpos = pos-1 + repeat + i = i+1 + f = dispatcher[byte(json,newpos+1)] -- parse value + pos = newpos+2 + ary[i] = f() + f, newpos = find(json, '^[ \n\r\t]*,[ \n\r\t]*', pos) -- check comma + until not newpos + + f, newpos = find(json, '^[ \n\r\t]*%]', pos) -- check closing bracket + if not newpos then + decode_error("no closing bracket of an array") + end + pos = newpos + end + + pos = pos+1 + if arraylen then -- commit the length of the array if `arraylen` is set + ary[0] = i + end + rec_depth = rec_depth - 1 + return ary + end + + -- objects + local function f_obj() + rec_depth = rec_depth + 1 + if rec_depth > 1000 then + decode_error('too deeply nested json (> 1000)') + end + local obj = {} + + f, pos = find(json, '^[ \n\r\t]*', pos) + pos = pos+1 + if byte(json, pos) ~= 0x7D then -- check closing bracket '}' which means the object empty + local newpos = pos-1 + + repeat + pos = newpos+1 + if byte(json, pos) ~= 0x22 then -- check '"' + decode_error("not key") + end + pos = pos+1 + local key = f_str(true) -- parse key + + -- optimized for compact json + -- c1, c2 == ':', or + -- c1, c2, c3 == ':', ' ', + f = f_err + do + local c1, c2, c3 = byte(json, pos, pos+3) + if c1 == 0x3A then + newpos = pos + if c2 == 0x20 then + newpos = newpos+1 + c2 = c3 + end + f = dispatcher[c2] + end + end + if f == f_err then -- read a colon and arbitrary number of spaces + f, newpos = find(json, '^[ \n\r\t]*:[ \n\r\t]*', pos) + if not newpos then + decode_error("no colon after a key") + end + f = dispatcher[byte(json, newpos+1)] + end + pos = newpos+2 + obj[key] = f() -- parse value + f, newpos = find(json, '^[ \n\r\t]*,[ \n\r\t]*', pos) + until not newpos + + f, newpos = find(json, '^[ \n\r\t]*}', pos) + if not newpos then + decode_error("no closing bracket of an object") + end + pos = newpos + end + + pos = pos+1 + rec_depth = rec_depth - 1 + return obj + end + + --[[ + The jump table to dispatch a parser for a value, + indexed by the code of the value's first char. + Nil key means the end of json. + --]] + dispatcher = { + f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err, + f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num, + f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err, + f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err, + f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err, + } + dispatcher[0] = f_err + dispatcher.__index = function() + decode_error("unexpected termination") + end + setmetatable(dispatcher, dispatcher) + + --[[ + run decoder + --]] + local function decode(json_, pos_, nullv_, arraylen_) + json, pos, nullv, arraylen = json_, pos_, nullv_, arraylen_ + rec_depth = 0 + + pos = pos or 1 + f, pos = find(json, '^[ \n\r\t]*', pos) + pos = pos+1 + + f = dispatcher[byte(json, pos)] + pos = pos+1 + local v = f() + + if pos_ then + return v, pos + else + f, pos = find(json, '^[ \n\r\t]*', pos) + if pos ~= #json then + decode_error('json ended') + end + return v + end + end + + return decode +end + +return newdecoder diff --git a/share/lua/5.3/lunajson/encoder.lua b/share/lua/5.3/lunajson/encoder.lua new file mode 100644 index 0000000..606767a --- /dev/null +++ b/share/lua/5.3/lunajson/encoder.lua @@ -0,0 +1,185 @@ +local error = error +local byte, find, format, gsub, match = string.byte, string.find, string.format, string.gsub, string.match +local concat = table.concat +local tostring = tostring +local pairs, type = pairs, type +local setmetatable = setmetatable +local huge, tiny = 1/0, -1/0 + +local f_string_esc_pat +if _VERSION == "Lua 5.1" then + -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly + f_string_esc_pat = '[^ -!#-[%]^-\255]' +else + f_string_esc_pat = '[\0-\31"\\]' +end + +local _ENV = nil + + +local function newencoder() + local v, nullv + local i, builder, visited + + local function f_tostring(v) + builder[i] = tostring(v) + i = i+1 + end + + local radixmark = match(tostring(0.5), '[^0-9]') + local delimmark = match(tostring(12345.12345), '[^0-9' .. radixmark .. ']') + if radixmark == '.' then + radixmark = nil + end + + local radixordelim + if radixmark or delimmark then + radixordelim = true + if radixmark and find(radixmark, '%W') then + radixmark = '%' .. radixmark + end + if delimmark and find(delimmark, '%W') then + delimmark = '%' .. delimmark + end + end + + local f_number = function(n) + if tiny < n and n < huge then + local s = format("%.17g", n) + if radixordelim then + if delimmark then + s = gsub(s, delimmark, '') + end + if radixmark then + s = gsub(s, radixmark, '.') + end + end + builder[i] = s + i = i+1 + return + end + error('invalid number') + end + + local doencode + + local f_string_subst = { + ['"'] = '\\"', + ['\\'] = '\\\\', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t', + __index = function(_, c) + return format('\\u00%02X', byte(c)) + end + } + setmetatable(f_string_subst, f_string_subst) + + local function f_string(s) + builder[i] = '"' + if find(s, f_string_esc_pat) then + s = gsub(s, f_string_esc_pat, f_string_subst) + end + builder[i+1] = s + builder[i+2] = '"' + i = i+3 + end + + local function f_table(o) + if visited[o] then + error("loop detected") + end + visited[o] = true + + local tmp = o[0] + if type(tmp) == 'number' then -- arraylen available + builder[i] = '[' + i = i+1 + for j = 1, tmp do + doencode(o[j]) + builder[i] = ',' + i = i+1 + end + if tmp > 0 then + i = i-1 + end + builder[i] = ']' + + else + tmp = o[1] + if tmp ~= nil then -- detected as array + builder[i] = '[' + i = i+1 + local j = 2 + repeat + doencode(tmp) + tmp = o[j] + if tmp == nil then + break + end + j = j+1 + builder[i] = ',' + i = i+1 + until false + builder[i] = ']' + + else -- detected as object + builder[i] = '{' + i = i+1 + local tmp = i + for k, v in pairs(o) do + if type(k) ~= 'string' then + error("non-string key") + end + f_string(k) + builder[i] = ':' + i = i+1 + doencode(v) + builder[i] = ',' + i = i+1 + end + if i > tmp then + i = i-1 + end + builder[i] = '}' + end + end + + i = i+1 + visited[o] = nil + end + + local dispatcher = { + boolean = f_tostring, + number = f_number, + string = f_string, + table = f_table, + __index = function() + error("invalid type value") + end + } + setmetatable(dispatcher, dispatcher) + + function doencode(v) + if v == nullv then + builder[i] = 'null' + i = i+1 + return + end + return dispatcher[type(v)](v) + end + + local function encode(v_, nullv_) + v, nullv = v_, nullv_ + i, builder, visited = 1, {}, {} + + doencode(v) + return concat(builder) + end + + return encode +end + +return newencoder diff --git a/share/lua/5.3/lunajson/sax.lua b/share/lua/5.3/lunajson/sax.lua new file mode 100644 index 0000000..bebe165 --- /dev/null +++ b/share/lua/5.3/lunajson/sax.lua @@ -0,0 +1,718 @@ +local setmetatable, tonumber, tostring = + setmetatable, tonumber, tostring +local floor, inf = + math.floor, math.huge +local mininteger, tointeger = + math.mininteger or nil, math.tointeger or nil +local byte, char, find, gsub, match, sub = + string.byte, string.char, string.find, string.gsub, string.match, string.sub + +local function _parse_error(pos, errmsg) + error("parse error at " .. pos .. ": " .. errmsg, 2) +end + +local f_str_ctrl_pat +if _VERSION == "Lua 5.1" then + -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly + f_str_ctrl_pat = '[^\32-\255]' +else + f_str_ctrl_pat = '[\0-\31]' +end + +local type, unpack = type, table.unpack or unpack + +local _ENV = nil + + +local function nop() end + +local function newparser(src, saxtbl) + local json, jsonnxt, rec_depth + local jsonlen, pos, acc = 0, 1, 0 + + -- `f` is the temporary for dispatcher[c] and + -- the dummy for the first return value of `find` + local dispatcher, f + + -- initialize + if type(src) == 'string' then + json = src + jsonlen = #json + jsonnxt = function() + json = '' + jsonlen = 0 + jsonnxt = nop + end + else + jsonnxt = function() + acc = acc + jsonlen + pos = 1 + repeat + json = src() + if not json then + json = '' + jsonlen = 0 + jsonnxt = nop + return + end + jsonlen = #json + until jsonlen > 0 + end + jsonnxt() + end + + local sax_startobject = saxtbl.startobject or nop + local sax_key = saxtbl.key or nop + local sax_endobject = saxtbl.endobject or nop + local sax_startarray = saxtbl.startarray or nop + local sax_endarray = saxtbl.endarray or nop + local sax_string = saxtbl.string or nop + local sax_number = saxtbl.number or nop + local sax_boolean = saxtbl.boolean or nop + local sax_null = saxtbl.null or nop + + --[[ + Helper + --]] + local function tryc() + local c = byte(json, pos) + if not c then + jsonnxt() + c = byte(json, pos) + end + return c + end + + local function parse_error(errmsg) + return _parse_error(acc + pos, errmsg) + end + + local function tellc() + return tryc() or parse_error("unexpected termination") + end + + local function spaces() -- skip spaces and prepare the next char + while true do + f, pos = find(json, '^[ \n\r\t]*', pos) + if pos ~= jsonlen then + pos = pos+1 + return + end + if jsonlen == 0 then + parse_error("unexpected termination") + end + jsonnxt() + end + end + + --[[ + Invalid + --]] + local function f_err() + parse_error('invalid value') + end + + --[[ + Constants + --]] + -- fallback slow constants parser + local function generic_constant(target, targetlen, ret, sax_f) + for i = 1, targetlen do + local c = tellc() + if byte(target, i) ~= c then + parse_error("invalid char") + end + pos = pos+1 + end + return sax_f(ret) + end + + -- null + local function f_nul() + if sub(json, pos, pos+2) == 'ull' then + pos = pos+3 + return sax_null(nil) + end + return generic_constant('ull', 3, nil, sax_null) + end + + -- false + local function f_fls() + if sub(json, pos, pos+3) == 'alse' then + pos = pos+4 + return sax_boolean(false) + end + return generic_constant('alse', 4, false, sax_boolean) + end + + -- true + local function f_tru() + if sub(json, pos, pos+2) == 'rue' then + pos = pos+3 + return sax_boolean(true) + end + return generic_constant('rue', 3, true, sax_boolean) + end + + --[[ + Numbers + Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp) + is captured as a number and its conformance to the JSON spec is checked. + --]] + -- deal with non-standard locales + local radixmark = match(tostring(0.5), '[^0-9]') + local fixedtonumber = tonumber + if radixmark ~= '.' then + if find(radixmark, '%W') then + radixmark = '%' .. radixmark + end + fixedtonumber = function(s) + return tonumber(gsub(s, '.', radixmark)) + end + end + + local function number_error() + return parse_error('invalid number') + end + + -- fallback slow parser + local function generic_number(mns) + local buf = {} + local i = 1 + local is_int = true + + local c = byte(json, pos) + pos = pos+1 + + local function nxt() + buf[i] = c + i = i+1 + c = tryc() + pos = pos+1 + end + + if c == 0x30 then + nxt() + if c and 0x30 <= c and c < 0x3A then + number_error() + end + else + repeat nxt() until not (c and 0x30 <= c and c < 0x3A) + end + if c == 0x2E then + is_int = false + nxt() + if not (c and 0x30 <= c and c < 0x3A) then + number_error() + end + repeat nxt() until not (c and 0x30 <= c and c < 0x3A) + end + if c == 0x45 or c == 0x65 then + is_int = false + nxt() + if c == 0x2B or c == 0x2D then + nxt() + end + if not (c and 0x30 <= c and c < 0x3A) then + number_error() + end + repeat nxt() until not (c and 0x30 <= c and c < 0x3A) + end + if c and (0x41 <= c and c <= 0x5B or + 0x61 <= c and c <= 0x7B or + c == 0x2B or c == 0x2D or c == 0x2E) then + number_error() + end + pos = pos-1 + + local num = char(unpack(buf)) + num = fixedtonumber(num) + if mns then + num = -num + if num == mininteger and is_int then + num = mininteger + end + end + return sax_number(num) + end + + -- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?` + local function f_zro(mns) + local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0 + + if num == '' then + if pos > jsonlen then + pos = pos - 1 + return generic_number(mns) + end + if c == '' then + if mns then + return sax_number(-0.0) + end + return sax_number(0) + end + + if c == 'e' or c == 'E' then + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + if c == '' then + pos = pos + #num + if pos > jsonlen then + pos = pos - #num - 1 + return generic_number(mns) + end + if mns then + return sax_number(-0.0) + end + return sax_number(0.0) + end + end + pos = pos-1 + return generic_number(mns) + end + + if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then + pos = pos-1 + return generic_number(mns) + end + + if c ~= '' then + if c == 'e' or c == 'E' then + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + end + if c ~= '' then + pos = pos-1 + return generic_number(mns) + end + end + + pos = pos + #num + if pos > jsonlen then + pos = pos - #num - 1 + return generic_number(mns) + end + c = fixedtonumber(num) + + if mns then + c = -c + end + return sax_number(c) + end + + -- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?` + local function f_num(mns) + pos = pos-1 + local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos) + if byte(num, -1) == 0x2E then -- error if ended with period + return generic_number(mns) + end + + if c ~= '' then + if c ~= 'e' and c ~= 'E' then + return generic_number(mns) + end + num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos) + if not num or c ~= '' then + return generic_number(mns) + end + end + + pos = pos + #num + if pos > jsonlen then + pos = pos - #num + return generic_number(mns) + end + c = fixedtonumber(num) + + if mns then + c = -c + if c == mininteger and not find(num, '[^0-9]') then + c = mininteger + end + end + return sax_number(c) + end + + -- skip minus sign + local function f_mns() + local c = byte(json, pos) or tellc() + if c then + pos = pos+1 + if c > 0x30 then + if c < 0x3A then + return f_num(true) + end + else + if c > 0x2F then + return f_zro(true) + end + end + end + parse_error("invalid number") + end + + --[[ + Strings + --]] + local f_str_hextbl = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, inf, inf, inf, inf, inf, inf, + inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, inf, inf, inf, inf, inf, inf, inf, + inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, + } + f_str_hextbl.__index = function() + return inf + end + setmetatable(f_str_hextbl, f_str_hextbl) + + local f_str_escapetbl = { + ['"'] = '"', + ['\\'] = '\\', + ['/'] = '/', + ['b'] = '\b', + ['f'] = '\f', + ['n'] = '\n', + ['r'] = '\r', + ['t'] = '\t' + } + f_str_escapetbl.__index = function() + parse_error("invalid escape sequence") + end + setmetatable(f_str_escapetbl, f_str_escapetbl) + + local function surrogate_first_error() + return parse_error("1st surrogate pair byte not continued by 2nd") + end + + local f_str_surrogate_prev = 0 + local function f_str_subst(ch, ucode) + if ch == 'u' then + local c1, c2, c3, c4, rest = byte(ucode, 1, 5) + ucode = f_str_hextbl[c1-47] * 0x1000 + + f_str_hextbl[c2-47] * 0x100 + + f_str_hextbl[c3-47] * 0x10 + + f_str_hextbl[c4-47] + if ucode ~= inf then + if ucode < 0x80 then -- 1byte + if rest then + return char(ucode, rest) + end + return char(ucode) + elseif ucode < 0x800 then -- 2bytes + c1 = floor(ucode / 0x40) + c2 = ucode - c1 * 0x40 + c1 = c1 + 0xC0 + c2 = c2 + 0x80 + if rest then + return char(c1, c2, rest) + end + return char(c1, c2) + elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes + c1 = floor(ucode / 0x1000) + ucode = ucode - c1 * 0x1000 + c2 = floor(ucode / 0x40) + c3 = ucode - c2 * 0x40 + c1 = c1 + 0xE0 + c2 = c2 + 0x80 + c3 = c3 + 0x80 + if rest then + return char(c1, c2, c3, rest) + end + return char(c1, c2, c3) + elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st + if f_str_surrogate_prev == 0 then + f_str_surrogate_prev = ucode + if not rest then + return '' + end + surrogate_first_error() + end + f_str_surrogate_prev = 0 + surrogate_first_error() + else -- surrogate pair 2nd + if f_str_surrogate_prev ~= 0 then + ucode = 0x10000 + + (f_str_surrogate_prev - 0xD800) * 0x400 + + (ucode - 0xDC00) + f_str_surrogate_prev = 0 + c1 = floor(ucode / 0x40000) + ucode = ucode - c1 * 0x40000 + c2 = floor(ucode / 0x1000) + ucode = ucode - c2 * 0x1000 + c3 = floor(ucode / 0x40) + c4 = ucode - c3 * 0x40 + c1 = c1 + 0xF0 + c2 = c2 + 0x80 + c3 = c3 + 0x80 + c4 = c4 + 0x80 + if rest then + return char(c1, c2, c3, c4, rest) + end + return char(c1, c2, c3, c4) + end + parse_error("2nd surrogate pair byte appeared without 1st") + end + end + parse_error("invalid unicode codepoint literal") + end + if f_str_surrogate_prev ~= 0 then + f_str_surrogate_prev = 0 + surrogate_first_error() + end + return f_str_escapetbl[ch] .. ucode + end + + local function f_str(iskey) + local pos2 = pos + local newpos + local str = '' + local bs + while true do + while true do -- search '\' or '"' + newpos = find(json, '[\\"]', pos2) + if newpos then + break + end + str = str .. sub(json, pos, jsonlen) + if pos2 == jsonlen+2 then + pos2 = 2 + else + pos2 = 1 + end + jsonnxt() + if jsonlen == 0 then + parse_error("unterminated string") + end + end + if byte(json, newpos) == 0x22 then -- break if '"' + break + end + pos2 = newpos+2 -- skip '\' + bs = true -- mark the existence of a backslash + end + str = str .. sub(json, pos, newpos-1) + pos = newpos+1 + + if find(str, f_str_ctrl_pat) then + parse_error("unescaped control string") + end + if bs then -- a backslash exists + -- We need to grab 4 characters after the escape char, + -- for encoding unicode codepoint to UTF-8. + -- As we need to ensure that every first surrogate pair byte is + -- immediately followed by second one, we grab upto 5 characters and + -- check the last for this purpose. + str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst) + if f_str_surrogate_prev ~= 0 then + f_str_surrogate_prev = 0 + parse_error("1st surrogate pair byte not continued by 2nd") + end + end + + if iskey then + return sax_key(str) + end + return sax_string(str) + end + + --[[ + Arrays, Objects + --]] + -- arrays + local function f_ary() + rec_depth = rec_depth + 1 + if rec_depth > 1000 then + parse_error('too deeply nested json (> 1000)') + end + sax_startarray() + + spaces() + if byte(json, pos) ~= 0x5D then -- check closing bracket ']' which means the array empty + local newpos + while true do + f = dispatcher[byte(json, pos)] -- parse value + pos = pos+1 + f() + f, newpos = find(json, '^[ \n\r\t]*,[ \n\r\t]*', pos) -- check comma + if not newpos then + f, newpos = find(json, '^[ \n\r\t]*%]', pos) -- check closing bracket + if newpos then + pos = newpos + break + end + spaces() -- since the current chunk can be ended, skip spaces toward following chunks + local c = byte(json, pos) + if c == 0x2C then -- check comma again + pos = pos+1 + spaces() + newpos = pos-1 + elseif c == 0x5D then -- check closing bracket again + break + else + parse_error("no closing bracket of an array") + end + end + pos = newpos+1 + if pos > jsonlen then + spaces() + end + end + end + + pos = pos+1 + rec_depth = rec_depth - 1 + return sax_endarray() + end + + -- objects + local function f_obj() + rec_depth = rec_depth + 1 + if rec_depth > 1000 then + parse_error('too deeply nested json (> 1000)') + end + sax_startobject() + + spaces() + if byte(json, pos) ~= 0x7D then -- check closing bracket '}' which means the object empty + local newpos + while true do + if byte(json, pos) ~= 0x22 then + parse_error("not key") + end + pos = pos+1 + f_str(true) -- parse key + f, newpos = find(json, '^[ \n\r\t]*:[ \n\r\t]*', pos) -- check colon + if not newpos then + spaces() -- read spaces through chunks + if byte(json, pos) ~= 0x3A then -- check colon again + parse_error("no colon after a key") + end + pos = pos+1 + spaces() + newpos = pos-1 + end + pos = newpos+1 + if pos > jsonlen then + spaces() + end + f = dispatcher[byte(json, pos)] + pos = pos+1 + f() -- parse value + f, newpos = find(json, '^[ \n\r\t]*,[ \n\r\t]*', pos) -- check comma + if not newpos then + f, newpos = find(json, '^[ \n\r\t]*}', pos) -- check closing bracket + if newpos then + pos = newpos + break + end + spaces() -- read spaces through chunks + local c = byte(json, pos) + if c == 0x2C then -- check comma again + pos = pos+1 + spaces() + newpos = pos-1 + elseif c == 0x7D then -- check closing bracket again + break + else + parse_error("no closing bracket of an object") + end + end + pos = newpos+1 + if pos > jsonlen then + spaces() + end + end + end + + pos = pos+1 + rec_depth = rec_depth - 1 + return sax_endobject() + end + + --[[ + The jump table to dispatch a parser for a value, + indexed by the code of the value's first char. + Key should be non-nil. + --]] + dispatcher = { + f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err, + f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num, + f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err, + f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err, + f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err, + f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err, + } + dispatcher[0] = f_err + + --[[ + public funcitons + --]] + local function run() + rec_depth = 0 + spaces() + f = dispatcher[byte(json, pos)] + pos = pos+1 + f() + end + + local function read(n) + if n < 0 then + error("the argument must be non-negative") + end + local pos2 = (pos-1) + n + local str = sub(json, pos, pos2) + while pos2 > jsonlen and jsonlen ~= 0 do + jsonnxt() + pos2 = pos2 - (jsonlen - (pos-1)) + str = str .. sub(json, pos, pos2) + end + if jsonlen ~= 0 then + pos = pos2+1 + end + return str + end + + local function tellpos() + return acc + pos + end + + return { + run = run, + tryc = tryc, + read = read, + tellpos = tellpos, + } +end + +local function newfileparser(fn, saxtbl) + local fp = io.open(fn) + local function gen() + local s + if fp then + s = fp:read(8192) + if not s then + fp:close() + fp = nil + end + end + return s + end + return newparser(gen, saxtbl) +end + +return { + newparser = newparser, + newfileparser = newfileparser +} diff --git a/test/call-citeproc-is.html b/test/call-citeproc-is.html new file mode 100644 index 0000000..4a9074b --- /dev/null +++ b/test/call-citeproc-is.html @@ -0,0 +1,30 @@ +

Some citations:

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

Multiple times, just to make sure.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

This should confuse the script.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

(???) says: “I don’t exist, but I shouldn’t crash the script.”

+
+
+

Díaz-León, Esa. 2013. “What Is Social Construction?” European Journal of Philosophy, 1–16. https://doi.org/10.1111/ejop.12033.

+
+
+

———. 2015. “In Defence of Historical Constructivism About Races.” Ergo, an Open Access Journal of Philosophy 2. https://doi.org/10.3998/ergo.12405314.0002.021.

+
+
+

———. 2016. “Woman as a Politically Significant Term: A Solution to the Puzzle.” Hypatia, February, n/a–n/a. https://doi.org/10.1111/hypa.12234.

+
+
+

Dotson, Kristie. 2016. “Word to the Wise: Notes on a Black Feminist Metaphilosophy of Race.” Philosophy Compass 11 (2):69–74. https://doi.org/10.1111/phc3.12268.

+
+
+

Haslanger, Sally. 2012. Resisting Reality: Social Construction and Social Critique. Oxford: Oxford University Press.

+
+
diff --git a/test/call-citeproc-should.html b/test/call-citeproc-should.html new file mode 100644 index 0000000..4a9074b --- /dev/null +++ b/test/call-citeproc-should.html @@ -0,0 +1,30 @@ +

Some citations:

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

Multiple times, just to make sure.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

This should confuse the script.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

(???) says: “I don’t exist, but I shouldn’t crash the script.”

+
+
+

Díaz-León, Esa. 2013. “What Is Social Construction?” European Journal of Philosophy, 1–16. https://doi.org/10.1111/ejop.12033.

+
+
+

———. 2015. “In Defence of Historical Constructivism About Races.” Ergo, an Open Access Journal of Philosophy 2. https://doi.org/10.3998/ergo.12405314.0002.021.

+
+
+

———. 2016. “Woman as a Politically Significant Term: A Solution to the Puzzle.” Hypatia, February, n/a–n/a. https://doi.org/10.1111/hypa.12234.

+
+
+

Dotson, Kristie. 2016. “Word to the Wise: Notes on a Black Feminist Metaphilosophy of Race.” Philosophy Compass 11 (2):69–74. https://doi.org/10.1111/phc3.12268.

+
+
+

Haslanger, Sally. 2012. Resisting Reality: Social Construction and Social Critique. Oxford: Oxford University Press.

+
+
diff --git a/test/call-citeproc.md b/test/call-citeproc.md new file mode 100644 index 0000000..a2495c8 --- /dev/null +++ b/test/call-citeproc.md @@ -0,0 +1,29 @@ +--- +call-citeproc: yes +... + +Some citations: + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +Multiple times, just to make sure. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +This should confuse the script. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +@nobody:0000nothing says: "I don't exist, but I shouldn't crash the script." diff --git a/test/doc-is.html b/test/doc-is.html new file mode 100644 index 0000000..4a9074b --- /dev/null +++ b/test/doc-is.html @@ -0,0 +1,30 @@ +

Some citations:

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

Multiple times, just to make sure.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

This should confuse the script.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

(???) says: “I don’t exist, but I shouldn’t crash the script.”

+
+
+

Díaz-León, Esa. 2013. “What Is Social Construction?” European Journal of Philosophy, 1–16. https://doi.org/10.1111/ejop.12033.

+
+
+

———. 2015. “In Defence of Historical Constructivism About Races.” Ergo, an Open Access Journal of Philosophy 2. https://doi.org/10.3998/ergo.12405314.0002.021.

+
+
+

———. 2016. “Woman as a Politically Significant Term: A Solution to the Puzzle.” Hypatia, February, n/a–n/a. https://doi.org/10.1111/hypa.12234.

+
+
+

Dotson, Kristie. 2016. “Word to the Wise: Notes on a Black Feminist Metaphilosophy of Race.” Philosophy Compass 11 (2):69–74. https://doi.org/10.1111/phc3.12268.

+
+
+

Haslanger, Sally. 2012. Resisting Reality: Social Construction and Social Critique. Oxford: Oxford University Press.

+
+
diff --git a/test/doc-should.html b/test/doc-should.html new file mode 100644 index 0000000..4a9074b --- /dev/null +++ b/test/doc-should.html @@ -0,0 +1,30 @@ +

Some citations:

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

Multiple times, just to make sure.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

This should confuse the script.

+

Haslanger (2012) says p.

+

Q (cf. Díaz-León 2013, 2015, 2016)!

+

Dotson (2016) says r.

+

(???) says: “I don’t exist, but I shouldn’t crash the script.”

+
+
+

Díaz-León, Esa. 2013. “What Is Social Construction?” European Journal of Philosophy, 1–16. https://doi.org/10.1111/ejop.12033.

+
+
+

———. 2015. “In Defence of Historical Constructivism About Races.” Ergo, an Open Access Journal of Philosophy 2. https://doi.org/10.3998/ergo.12405314.0002.021.

+
+
+

———. 2016. “Woman as a Politically Significant Term: A Solution to the Puzzle.” Hypatia, February, n/a–n/a. https://doi.org/10.1111/hypa.12234.

+
+
+

Dotson, Kristie. 2016. “Word to the Wise: Notes on a Black Feminist Metaphilosophy of Race.” Philosophy Compass 11 (2):69–74. https://doi.org/10.1111/phc3.12268.

+
+
+

Haslanger, Sally. 2012. Resisting Reality: Social Construction and Social Critique. Oxford: Oxford University Press.

+
+
diff --git a/test/doc.md b/test/doc.md new file mode 100644 index 0000000..075df19 --- /dev/null +++ b/test/doc.md @@ -0,0 +1,25 @@ +Some citations: + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +Multiple times, just to make sure. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +This should confuse the script. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +@nobody:0000nothing says: "I don't exist, but I shouldn't crash the script." diff --git a/test/dont-call-citeproc-is.html b/test/dont-call-citeproc-is.html new file mode 100644 index 0000000..1aa7a51 --- /dev/null +++ b/test/dont-call-citeproc-is.html @@ -0,0 +1,13 @@ +

Some citations:

+

@haslanger:2012resisting says p.

+

Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]!

+

Dotson [-@dotson:2016word] says r.

+

Multiple times, just to make sure.

+

@haslanger:2012resisting says p.

+

Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]!

+

Dotson [-@dotson:2016word] says r.

+

This should confuse the script.

+

@haslanger:2012resisting says p.

+

Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]!

+

Dotson [-@dotson:2016word] says r.

+

@nobody:0000nothing says: “I don’t exist, but I shouldn’t crash the script.”

diff --git a/test/dont-call-citeproc-should.html b/test/dont-call-citeproc-should.html new file mode 100644 index 0000000..1aa7a51 --- /dev/null +++ b/test/dont-call-citeproc-should.html @@ -0,0 +1,13 @@ +

Some citations:

+

@haslanger:2012resisting says p.

+

Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]!

+

Dotson [-@dotson:2016word] says r.

+

Multiple times, just to make sure.

+

@haslanger:2012resisting says p.

+

Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]!

+

Dotson [-@dotson:2016word] says r.

+

This should confuse the script.

+

@haslanger:2012resisting says p.

+

Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]!

+

Dotson [-@dotson:2016word] says r.

+

@nobody:0000nothing says: “I don’t exist, but I shouldn’t crash the script.”

diff --git a/test/dont-call-citeproc.md b/test/dont-call-citeproc.md new file mode 100644 index 0000000..ec9d76a --- /dev/null +++ b/test/dont-call-citeproc.md @@ -0,0 +1,29 @@ +--- +call-citeproc: no +... + +Some citations: + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +Multiple times, just to make sure. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +This should confuse the script. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +@nobody:0000nothing says: "I don't exist, but I shouldn't crash the script." diff --git a/test/items.rdf b/test/items.rdf new file mode 100644 index 0000000..dd5c2d4 --- /dev/null +++ b/test/items.rdf @@ -0,0 +1,210 @@ + + + journalArticle + + + + + + Díaz-León + Esa + + + + + + + + http://onlinelibrary.wiley.com/doi/10.1111/ejop.12033/abstract + + + © 2013 John Wiley & Sons Ltd + 1-16 + 2013 + 2015-11-14 18:07:41 + Wiley Online Library + en + In this paper I discuss the question of what it means to say that a property is socially constructed. I focus on an influential project that many social constructivists are engaged in, namely, arguing against the inevitability of a trait, and I examine several recent characterizations of social construction, with the aim of assessing which one is more suited to the task. + What Is Social Construction? + + + European Journal of Philosophy + ISSN 1468-0378 + Eur J Philos + DOI 10.1111/ejop.12033 + + + attachment + Díaz-León 2013.pdf + application/pdf + + + book + + + + + Oxford + + + Oxford University Press + + + + + + + Haslanger + Sally + + + + + + ISBN 978-0-19-989262-4 + 2012 + Resisting Reality: Social Construction and Social Critique + Resisting Reality + + + attachment + Haslanger 2012.pdf + application/pdf + + + journalArticle + + + + + + Díaz-León + Esa + + + + + + + + http://hdl.handle.net/2027/spo.12405314.0002.021 + + + 2015 + In Defence of Historical Constructivism about Races + + + 2 + Ergo, an Open Access Journal of Philosophy + ISSN 2330-4014 + DOI 10.3998/ergo.12405314.0002.021 + + + attachment + Díaz-León 2015.pdf + application/pdf + + + journalArticle + + + + + + Díaz-León + Esa + + + + + + + + http://onlinelibrary.wiley.com.uaccess.univie.ac.at/doi/10.1111/hypa.12234/abstract + + + © by Hypatia, Inc. + n/a-n/a + February 1, 2016 + 2016-02-18 13:43:39 + Wiley Online Library + en + What does woman mean? According to two competing views, it can be seen as a sex term or as a gender term. Recently, Jennifer Saul has put forward a contextualist view, according to which woman can have different meanings in different contexts. The main motivation for this view seems to involve moral and political considerations, namely, that this view can do justice to the claims of trans women. Unfortunately, Saul argues, on further reflection the contextualist view fails to do justice to those moral and political claims that motivated the view in the first place. In this article I argue that there is a version of the contextualist view that can indeed capture those moral and political aims, and in addition, I use this case to illustrate an important and more general claim, namely, that moral and political considerations can be relevant to the descriptive project of finding out what certain politically significant terms actually mean. + Woman as a Politically Significant Term: A Solution to the Puzzle + Woman as a Politically Significant Term + + + Hypatia + ISSN 1527-2001 + Hypatia + DOI 10.1111/hypa.12234 + + + attachment + Díaz-León 2016.pdf + application/pdf + + + journalArticle + + + + + + Dotson + Kristie + + + + + + + + + http://onlinelibrary.wiley.com/doi/10.1111/phc3.12268/abstract + + + 69-74 + February 1, 2016 + 2017-07-25 21:16:29 + Wiley Online Library + en + It is not uncommon to ask a race and gender-based question of a philosopher of race, only to hear ‘I do race, not gender’. To the ears of many Black feminists, this sounds, to be frank, utterly foolish. Here, I identify three metaphilosophical assumptions, i.e. the disaggregation, fundamentality and transcendental assumptions, that aid in underwriting the ability to use the statement, ‘I do race, not gender’, as a means for avoiding gender-based questions in ‘race talks’. Then, I gesture to a reason to reject the fundamentality and transcendental assumptions, i.e. the promotion of intellectual slumming. + Word to the Wise: Notes on a Black Feminist Metaphilosophy of Race + Word to the Wise + + + 11 + 2 + Philosophy Compass + ISSN 1747-9991 + Philosophy Compass + DOI 10.1111/phc3.12268 + + + attachment + Dotson 2016.pdf + application/pdf + + + attachment + + + http://onlinelibrary.wiley.com/doi/10.1111/phc3.12268/abstract + + + 2017-07-25 21:16:32 + Snapshot + 1 + text/html + + diff --git a/test/long.md b/test/long.md new file mode 100644 index 0000000..3691f3a --- /dev/null +++ b/test/long.md @@ -0,0 +1,121 @@ + +# The Fox + +The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. + +Quick, Baz, get my woven flax jodhpurs! "Now fax quiz Jack! " my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV for luck. + +A wizard’s job is to vex chumps quickly in fog. Watch "Jeopardy! ", Alex Trebek's fun TV quiz game. Woven silk pyjamas exchanged for blue quartz. Brawny gods just flocked up to quiz and vex him. Adjusting quiver and bow, Zompyc[1] killed the fox. My faxed joke won a pager in the cable TV quiz show. Amazingly few discotheques provide jukeboxes. My girl wove six dozen plaid jackets before she quit. Six big devils from Japan quickly forgot how to waltz. + +Big July earthquakes confound zany experimental vow. Foxy parsons quiz and cajole the lovably dim wiki-girl. Have a pick: twenty six letters - no forcing a jumbled quiz! Crazy Fredericka bought many very exquisite opal jewels. Sixty zippers were quickly picked from the woven jute bag. A quick movement of the enemy will jeopardize six gunboats. All questions asked by five watch experts amazed the judge. Jack quietly moved up front and seized the big ball of wax. The quick, brown fox jumps over a lazy dog. + +DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! "Now fax quiz Jack! " my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV + +Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. + +Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. + +Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. + +A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. + +Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. + +Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. + +Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. + +Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. + +A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. + +Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. + +Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. + +Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. + +Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. + +A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. + +Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es. Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. + + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. + +Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. + +Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. + +Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. + +Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis + +Some citations: + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. + +Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. + +Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. + +Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. + +Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis + +Multiple times, just to make sure. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +This should confuse the script. + +@haslanger:2012resisting says p. + +Q [cf. @díaz-león:2013what; @díaz-león:2015defence; @díaz-león:2016woman]! + +Dotson [-@dotson:2016word] says r. + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. + +Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. + +Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. + +Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut eros et nisl sagittis vestibulum. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Sed lectus. Donec mollis hendrerit risus. Phasellus nec sem in justo pellentesque facilisis. Etiam imperdiet imperdiet orci. Nunc nec neque. + +Phasellus leo dolor, tempus non, auctor et, hendrerit quis, nisi. Curabitur ligula sapien, tincidunt non, euismod vitae, posuere imperdiet, leo. Maecenas malesuada. Praesent congue erat at massa. Sed cursus turpis vitae tortor. Donec posuere vulputate arcu. Phasellus accumsan cursus velit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed aliquam, nisi quis porttitor congue, elit erat euismod orci, ac placerat dolor lectus quis orci. Phasellus consectetuer vestibulum elit. Aenean tellus metus, bibendum sed, posuere ac, mattis non, nunc. Vestibulum fringilla pede sit amet augue. In turpis. Pellentesque posuere. Praesent turpis. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Donec elit libero, sodales nec, volutpat a, suscipit non, turpis. Nullam sagittis. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce id purus. Ut varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis + +# A change of tone + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + +Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. + +Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + +Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Sed ut + +## Some structure + +One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. + +"What's happened to me? " he thought. It wasn't a dream. His room, a proper human room although a little too small, lay peacefully between its four familiar walls. A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. + +It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops of rain could be heard hitting the pane, which made him feel quite sad. "How about if I sleep a little bit longer and forget all this nonsense", he thought, but that was something he was unable to do because he was used to sleeping on his right, and in his present state couldn't get into that position. + +However hard he threw himself onto his right, he always rolled back to where he was. He must have tried it a hundred times, shut his eyes so that he wouldn't have to look at the floundering legs, and only stopped when he began to feel a mild, dull pain there that he had never felt before. "Oh, God", he thought, "what a strenuous career it is that I've chosen! Travelling day in and day out. + +Doing business like this takes much more effort than doing your own business at home, and on top of that there's the curse of travelling, worries about making train connections, bad and irregular food, contact with different people all the time so that you can never get to know anyone or become friendly with them. It can all go to Hell! " He felt a slight itch up on his belly; pushed himself slowly up on his back towards the headboard so that he could lift his head better; found where the itch was, and saw that it was covered with lots of little white spots which he didn't know what to make of; and when he tried to feel the place with one of his legs he drew it quickly back because as soon as he touched it he was overcome by a cold shudder. He slid back into his former position. "Getting up early all the time", he thought, "it makes you stupid. You've got