From 4fb08e875200e92d5f680bf3c1cd5b06a86893a0 Mon Sep 17 00:00:00 2001 From: William Durand Date: Fri, 19 Aug 2016 17:39:21 +0200 Subject: [PATCH 1/7] Add first version of BibTeX support * new cite plugin (that enhances the fence block parser + add support for [@citation_ref]) * bibtex lib to parse and format references --- app/__tests__/bibtex-test.js | 133 ++++++++++++++ app/__tests__/fixtures/bib/article.bib | 20 +++ app/__tests__/fixtures/bib/book.bib | 12 ++ app/__tests__/fixtures/bib/inproceedings.bib | 14 ++ app/__tests__/fixtures/bib/misc.bib | 9 + app/__tests__/fixtures/bib/multi-entries.bib | 28 +++ app/__tests__/fixtures/bib/multi-same.bib | 41 +++++ app/bibtex.js | 180 +++++++++++++++++++ app/components/loaders/Preview.jsx | 1 + app/markdown-it-plugins/cite.js | 177 ++++++++++++++++++ app/scss/components/_preview.scss | 10 ++ package.json | 1 + 12 files changed, 626 insertions(+) create mode 100644 app/__tests__/bibtex-test.js create mode 100644 app/__tests__/fixtures/bib/article.bib create mode 100644 app/__tests__/fixtures/bib/book.bib create mode 100644 app/__tests__/fixtures/bib/inproceedings.bib create mode 100644 app/__tests__/fixtures/bib/misc.bib create mode 100644 app/__tests__/fixtures/bib/multi-entries.bib create mode 100644 app/__tests__/fixtures/bib/multi-same.bib create mode 100644 app/bibtex.js create mode 100644 app/markdown-it-plugins/cite.js diff --git a/app/__tests__/bibtex-test.js b/app/__tests__/bibtex-test.js new file mode 100644 index 00000000..34200cfd --- /dev/null +++ b/app/__tests__/bibtex-test.js @@ -0,0 +1,133 @@ +import { expect } from 'chai'; +import path from 'path'; +import fs from 'fs'; + +import Bibtex from '../bibtex'; + +// see: https://github.com/mochajs/mocha/issues/1847 +const { describe, it } = global; + + +function fixture(name) { + const content = fs.readFileSync( + path.join(__dirname, `./fixtures/bib/${name}.bib`), + { encoding: 'utf8' } + ); + + return content + .split('--- EXPECTED ---\n') + .map((part) => part.trim()); +} + +describe('bibtex', () => { + const bibtex = new Bibtex(); + + describe('normalizeAuthors()', () => { + it('should normalize an author without comma and multiple names', () => { + const authors = 'Victoria J. Hodge'; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(1); + expect(result[0]).to.eql({ fullname: 'Hodge, V. J.', lastname: 'Hodge' }); + }); + + it('should normalize an author without comma', () => { + const authors = 'William Durand'; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(1); + expect(result[0]).to.eql({ fullname: 'Durand, W.', lastname: 'Durand' }); + }); + + it('should normalize multiple authors without comma', () => { + const authors = 'William Durand and Sebastien Salva'; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(2); + expect(result[0]).to.eql({ fullname: 'Durand, W.', lastname: 'Durand' }); + expect(result[1]).to.eql({ fullname: 'Salva, S.', lastname: 'Salva' }); + }); + + it('should normalize multiple authors with comma', () => { + const authors = 'Hunter, Sarah and Corbett, Matthew and Denise, Hubert'; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(3); + expect(result[0]).to.eql({ fullname: 'Hunter, S.', lastname: 'Hunter' }); + expect(result[1]).to.eql({ fullname: 'Corbett, M.', lastname: 'Corbett' }); + expect(result[2]).to.eql({ fullname: 'Denise, H.', lastname: 'Denise' }); + }); + }); + + describe('getFirstAuthor()', () => { + it('should return the first author', () => { + const result = bibtex.getFirstAuthor('William Durand and Sebastien Salva'); + + expect(result).to.eql({ fullname: 'Durand, W.', lastname: 'Durand' }); + }); + + it('should return the first author', () => { + const authors = 'Victoria J. Hodge'; + const result = bibtex.getFirstAuthor(authors); + + expect(result).to.eql({ fullname: 'Hodge, V. J.', lastname: 'Hodge' }); + }); + + it('should return the first author', () => { + const authors = 'Hunter, Sarah and Corbett, Matthew'; + const result = bibtex.getFirstAuthor(authors); + + expect(result).to.eql({ fullname: 'Hunter, S.', lastname: 'Hunter' }); + }); + }); + + describe('parse()', () => { + it('should parse inproceedings', () => { + const [ content, expected ] = fixture('inproceedings'); + const result = (new Bibtex()).parse(content); + + expect(result).to.have.length(1); + expect(result[0].html).to.equal(expected); + }); + + it('should parse articles', () => { + const [ content, expected ] = fixture('article'); + const result = (new Bibtex()).parse(content); + + expect(result).to.have.length(1); + expect(result[0].html).to.equal(expected); + }); + + it('should parse multiple bibtex entries', () => { + const [ content, expected ] = fixture('multi-entries'); + const result = (new Bibtex()).parse(content); + + expect(result).to.have.length(2); + expect(result.map((e) => e.html).join('\n\n')).to.equal(expected); + }); + + it('should parse books', () => { + const [ content, expected ] = fixture('book'); + const result = (new Bibtex()).parse(content); + + expect(result).to.have.length(1); + expect(result[0].html).to.equal(expected); + }); + + it('should parse misc (fallback)', () => { + const [ content, expected ] = fixture('misc'); + const result = (new Bibtex()).parse(content); + + expect(result).to.have.length(1); + expect(result[0].html).to.equal(expected); + }); + + it('should return the same entry exactly once', () => { + const [ content, expected ] = fixture('multi-same'); + const result = (new Bibtex()).parse(content); + + expect(result).to.have.length(2); + expect(result.map((e) => e.html).join('\n\n')).to.equal(expected); + }); + }); +}); diff --git a/app/__tests__/fixtures/bib/article.bib b/app/__tests__/fixtures/bib/article.bib new file mode 100644 index 00000000..05524f42 --- /dev/null +++ b/app/__tests__/fixtures/bib/article.bib @@ -0,0 +1,20 @@ +@article{hunter_ebi_2014, + title = {{EBI} metagenomics—a new resource for the analysis and archiving of metagenomic data}, + volume = {42}, + issn = {0305-1048, 1362-4962}, + url = {http://nar.oxfordjournals.org/content/42/D1/D600}, + doi = {10.1093/nar/gkt961}, + abstract = {Metagenomics is a relatively recently established but rapidly expanding field that uses high-throughput next-generation sequencing technologies to characterize the microbial communities inhabiting different ecosystems (including oceans, lakes, soil, tundra, plants and body sites). Metagenomics brings with it a number of challenges, including the management, analysis, storage and sharing of data. In response to these challenges, we have developed a new metagenomics resource (http://www.ebi.ac.uk/metagenomics/) that allows users to easily submit raw nucleotide reads for functional and taxonomic analysis by a state-of-the-art pipeline, and have them automatically stored (together with descriptive, standards-compliant metadata) in the European Nucleotide Archive.}, + language = {en}, + number = {D1}, + urldate = {2015-01-08}, + journal = {Nucleic Acids Research}, + author = {Hunter, Sarah and Corbett, Matthew and Denise, Hubert and Fraser, Matthew and Gonzalez-Beltran, Alejandra and Hunter, Christopher and Jones, Philip and Leinonen, Rasko and McAnulla, Craig and Maguire, Eamonn and Maslen, John and Mitchell, Alex and Nuka, Gift and Oisel, Arnaud and Pesseat, Sebastien and Radhakrishnan, Rajesh and Rocca-Serra, Philippe and Scheremetjew, Maxim and Sterk, Peter and Vaughan, Daniel and Cochrane, Guy and Field, Dawn and Sansone, Susanna-Assunta}, + month = jan, + year = {2014}, + pmid = {24165880}, + pages = {D600--D606}, + annote = {Description de l'interface web d'EBI metagenomics} +} +--- EXPECTED --- +Hunter, S., Corbett, M., Denise, H., Fraser, M., Gonzalez-Beltran, A. & Hunter, C. et al. (2014). EBI metagenomics—a new resource for the analysis and archiving of metagenomic data. Nucleic Acids Research, 42(D1), D600—D606. http://dx.doi.org/10.1093/nar/gkt961 diff --git a/app/__tests__/fixtures/bib/book.bib b/app/__tests__/fixtures/bib/book.bib new file mode 100644 index 00000000..9b9f8674 --- /dev/null +++ b/app/__tests__/fixtures/bib/book.bib @@ -0,0 +1,12 @@ +@book{Warmer:1998:OCL:291202, + author = {Warmer, Jos and Kleppe, Anneke}, + title = {The Object Constraint Language: Precise Modeling + with UML}, + year = {1999}, + isbn = {0-201-37940-6}, + publisher = {Addison-Wesley Longman Publishing Co., + Inc.}, + address = {Boston, MA, USA}, +} +--- EXPECTED --- +Warmer, J. & Kleppe, A. (1999). The Object Constraint Language: Precise Modeling with UML. Addison-Wesley Longman Publishing Co., Inc. diff --git a/app/__tests__/fixtures/bib/inproceedings.bib b/app/__tests__/fixtures/bib/inproceedings.bib new file mode 100644 index 00000000..8f427639 --- /dev/null +++ b/app/__tests__/fixtures/bib/inproceedings.bib @@ -0,0 +1,14 @@ +@inproceedings{DBLP:conf/fm/DurandS15, + author = {William Durand and S{\'{e}}bastien Salva}, + title = {Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems}, + booktitle = {{FM} 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings}, + pages = {577--580}, + year = {2015}, + url = {http://dx.doi.org/10.1007/978-3-319-19249-9_36}, + doi = {10.1007/978-3-319-19249-9_36}, + timestamp = {Tue, 26 May 2015 14:26:29 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/fm/DurandS15}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +--- EXPECTED --- +Durand, W. & Salva, S. (2015). Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems. In FM 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings (pp. 577—580). http://dx.doi.org/10.1007/978-3-319-19249-9_36 diff --git a/app/__tests__/fixtures/bib/misc.bib b/app/__tests__/fixtures/bib/misc.bib new file mode 100644 index 00000000..144ff331 --- /dev/null +++ b/app/__tests__/fixtures/bib/misc.bib @@ -0,0 +1,9 @@ +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +--- EXPECTED --- +Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf diff --git a/app/__tests__/fixtures/bib/multi-entries.bib b/app/__tests__/fixtures/bib/multi-entries.bib new file mode 100644 index 00000000..5c83c823 --- /dev/null +++ b/app/__tests__/fixtures/bib/multi-entries.bib @@ -0,0 +1,28 @@ +@inproceedings{DBLP:conf/fm/DurandS15, + author = {William Durand and S{\'{e}}bastien Salva}, + title = {Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems}, + booktitle = {{FM} 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings}, + pages = {577--580}, + year = {2015}, + url = {http://dx.doi.org/10.1007/978-3-319-19249-9_36}, + doi = {10.1007/978-3-319-19249-9_36}, + timestamp = {Tue, 26 May 2015 14:26:29 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/fm/DurandS15}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@article{1695852, + author = {Victoria J. Hodge and Jim Austin}, + title = {A Survey of Outlier Detection Methodologies}, + journal = {Artificial Intelligence Review}, + volume = {22}, + year = {2004}, + pages = {85--126}, + issue = {2}, + doi = {http://dx.doi.org/10.1023/B:AIRE.0000045502.10941.a9}, + masid = {1695852} +} +--- EXPECTED --- +Durand, W. & Salva, S. (2015). Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems. In FM 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings (pp. 577—580). http://dx.doi.org/10.1007/978-3-319-19249-9_36 + +Hodge, V. J. & Austin, J. (2004). A Survey of Outlier Detection Methodologies. Artificial Intelligence Review, 22(2), 85—126. http://dx.doi.org/10.1023/B:AIRE.0000045502.10941.a9 diff --git a/app/__tests__/fixtures/bib/multi-same.bib b/app/__tests__/fixtures/bib/multi-same.bib new file mode 100644 index 00000000..13b99f58 --- /dev/null +++ b/app/__tests__/fixtures/bib/multi-same.bib @@ -0,0 +1,41 @@ +@inproceedings{DBLP:conf/fm/DurandS15, + author = {William Durand and S{\'{e}}bastien Salva}, + title = {Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems}, + booktitle = {{FM} 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings}, + pages = {577--580}, + year = {2015}, + url = {http://dx.doi.org/10.1007/978-3-319-19249-9_36}, + doi = {10.1007/978-3-319-19249-9_36}, + timestamp = {Tue, 26 May 2015 14:26:29 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/fm/DurandS15}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@article{1695852, + author = {Victoria J. Hodge and Jim Austin}, + title = {A Survey of Outlier Detection Methodologies}, + journal = {Artificial Intelligence Review}, + volume = {22}, + year = {2004}, + pages = {85--126}, + issue = {2}, + doi = {http://dx.doi.org/10.1023/B:AIRE.0000045502.10941.a9}, + masid = {1695852} +} + +@inproceedings{DBLP:conf/fm/DurandS15, + author = {William Durand and S{\'{e}}bastien Salva}, + title = {Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems}, + booktitle = {{FM} 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings}, + pages = {577--580}, + year = {2015}, + url = {http://dx.doi.org/10.1007/978-3-319-19249-9_36}, + doi = {10.1007/978-3-319-19249-9_36}, + timestamp = {Tue, 26 May 2015 14:26:29 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/fm/DurandS15}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +--- EXPECTED --- +Durand, W. & Salva, S. (2015). Autofunk: An Inference-Based Formal Model Generation Framework for Production Systems. In FM 2015: Formal Methods - 20th International Symposium, Oslo, Norway, June 24-26, 2015, Proceedings (pp. 577—580). http://dx.doi.org/10.1007/978-3-319-19249-9_36 + +Hodge, V. J. & Austin, J. (2004). A Survey of Outlier Detection Methodologies. Artificial Intelligence Review, 22(2), 85—126. http://dx.doi.org/10.1023/B:AIRE.0000045502.10941.a9 diff --git a/app/bibtex.js b/app/bibtex.js new file mode 100644 index 00000000..062079d0 --- /dev/null +++ b/app/bibtex.js @@ -0,0 +1,180 @@ +import parser from 'bibtex-parse-js'; + + +export default class Bibtex { + + parse(bibtex) { + const entries = parser.toJSON(bibtex); + + const keys = []; + const results = []; + for (let i = 0; i < entries.length; i++) { + const e = entries[i]; + const key = e.citationKey; + const firstAuthor = this.getFirstAuthor(e.entryTags.author); + + if (undefined === keys.find((k) => key === k)) { + keys.push(key); + + results.push({ + key, + firstAuthor, + html: this.formatHtmlEntry(e.entryType, e.entryTags), + htmlKey: this.formatHtmlKey(firstAuthor, e.entryTags.year), + }); + } + } + + return results + .sort((e1, e2) => + e1.firstAuthor.lastname.localeCompare(e2.firstAuthor.lastname) + ) + .map((e) => ( + { key: e.key, html: e.html, htmlKey: e.htmlKey } + )); + } + + formatHtmlEntry(type, data) { + let content; + switch (type.toLowerCase()) { + case 'inproceedings': + content = [ + `${this.formatAuthors(data.author)} (${data.year}). ${data.title}.`, + `In ${data.booktitle} (pp. ${data.pages}). ${this.urlize(data.url)}`, + ].join(' '); + break; + + case 'article': + content = [ + `${this.formatAuthors(data.author)} (${data.year}).`, + `${data.title}.`, + `${data.journal},`, + `${data.volume}${this.optional(data.issue, data.number)},`, + `${data.pages}.`, + `${this.urlizeDOI(data.doi)}`, + ].join(' '); + break; + + case 'book': + content = [ + `${this.formatAuthors(data.author)} (${data.year}).`, + `${data.title}.`, + `${data.publisher}`, + ].join(' '); + break; + + case 'misc': + default: + content = [ + `${this.formatAuthors(data.author)} (${data.year}).`, + `${data.title}.`, + `${data.note || ''}`, + ].join(' '); + + } + + return this.replaceLaTeXChars(content); + } + + getFirstAuthor(authors) { + return this.normalizeAuthors(authors)[0]; + } + + formatHtmlKey(firstAuthor, year) { + return `(${firstAuthor.lastname} et al., ${year})`; + } + + normalizeAuthors(authors) { + const parts = authors.split(/\sand\s/); + + return parts.map((fullname) => { + let otherNames; + let lastname; + if (/,/.test(fullname)) { + const subparts = fullname.split(/,/); + lastname = subparts[0].trim(); + otherNames = subparts[1]; + } else { + const subparts = fullname.split(/\s/); + lastname = subparts[subparts.length - 1].trim(); + otherNames = subparts.slice(0, subparts.length - 1).join(' '); + } + + otherNames = otherNames + .trim() + .split(/\s/) + .filter((p) => '' !== p) + .map((p) => + `${p.charAt(0).toUpperCase()}.` + ) + .join(' '); + + return { + fullname: `${lastname}, ${otherNames}`, + lastname, + }; + }); + } + + formatAuthors(authors) { + const normalizedAuthors = this.normalizeAuthors(authors); + const truncate = 6 < normalizedAuthors.length; + + let authorsToDisplay = normalizedAuthors; + if (truncate) { + authorsToDisplay = normalizedAuthors.splice(0, 6); + } + + // because we have to put `author, author, author & last author name` + const lastAuthorToDisplay = authorsToDisplay.pop(); + + let authorsString = authorsToDisplay + .map((a) => a.fullname) + .join(', ') + .concat(` & ${lastAuthorToDisplay.fullname}`); + + if (truncate) { + authorsString += ' et al.'; + } + + return this.replaceLaTeXChars(authorsString); + } + + optional(string, fallback) { + let str = string; + + if (undefined === str) { + str = fallback; + } + + return undefined !== str ? `(${str})` : ''; + } + + urlizeDOI(string) { + let str = string; + if (!/^https?:\/\//.test(str)) { + str = `http://dx.doi.org/${str}`; + } + + return this.urlize(str); + } + + urlize(string) { + if (/^https?:\/\//.test(string)) { + return `${string}`; + } + + return string; + } + + replaceLaTeXChars(string) { + return string + .replace(/\\'e/g, 'é') + .replace(/\\'\{e\}/g, 'é') + .replace(/\{/g, '') + .replace(/\}/g, '') + .replace(/--/g, '—') + .replace(/\s+/g, ' ') + ; + } +} diff --git a/app/components/loaders/Preview.jsx b/app/components/loaders/Preview.jsx index dc1e5275..b4251beb 100644 --- a/app/components/loaders/Preview.jsx +++ b/app/components/loaders/Preview.jsx @@ -21,6 +21,7 @@ export default () => new Promise(resolve => { require('markdown-it-abbr'), require('markdown-it-katex'), require('markdown-it-classy'), + require('../../markdown-it-plugins/cite'), ], markdownItContainer: require('markdown-it-container'), markdownItTaskLists: require('markdown-it-task-lists'), diff --git a/app/markdown-it-plugins/cite.js b/app/markdown-it-plugins/cite.js new file mode 100644 index 00000000..7527a795 --- /dev/null +++ b/app/markdown-it-plugins/cite.js @@ -0,0 +1,177 @@ +/* eslint no-param-reassign: 0, yoda: 0, no-continue: 0 */ +import Bibtex from '../bibtex'; + + +module.exports = (md) => { + const defaultRender = md.renderer.rules.fence; + const bibtex = new Bibtex(); + + // override fence renderer for `cite` + md.renderer.rules.fence = (tokens, idx, options, env, self) => { + const token = tokens[idx]; + + if ('cite' === token.info) { + const entries = bibtex.parse(md.utils.escapeHtml(token.content)); + + return entries.map( + (entry) => `

${entry.html}

` + ).join('\n'); + } + + // pass token to default renderer. + return defaultRender(tokens, idx, options, env, self); + }; + + // renderer for cite references `[@citation_ref]` + md.renderer.rules.cite_ref = (tokens, idx) => { // also take: options, env, self + const token = tokens[idx]; + const key = token.meta.key; + + if (!token.meta.htmlKey || null === token.meta.htmlKey) { + return `[@${key}]`; + } + + return token.meta.htmlKey; + }; + + // override fence block parser + md.block.ruler.at('fence', (state, startLine, endLine, silent) => { + let len; + let nextLine; + let mem; + let haveEndMarker = false; + let pos = state.bMarks[startLine] + state.tShift[startLine]; + let max = state.eMarks[startLine]; + + if (pos + 3 > max) { return false; } + + const marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + const markup = state.src.slice(mem, pos); + const params = state.src.slice(pos, max); + + if (params.indexOf('`') >= 0) { return false; } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + + const token = state.push('fence', 'code', 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [startLine, state.line]; + + if (!state.env.citations) { + state.env.citations = []; + } + + if ('cite' === token.info) { + state.env.citations = state.env.citations.concat( + bibtex.parse(md.utils.escapeHtml(token.content)) + ); + } + + return true; + }, { alt: ['paragraph', 'reference', 'blockquote', 'list'] }); + + // inline rule to parse citation references `[@citation]` + md.inline.ruler.after('image', 'cite_ref', (state, silent) => { + let pos; + let token; + const max = state.posMax; + const start = state.pos; + + // should be at least 4 chars - "[^x]" + if (start + 3 > max) { return false; } + + if (!state.env.citations) { return false; } + if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } + if (state.src.charCodeAt(start + 1) !== 0x40/* @ */) { return false; } + + for (pos = start + 2; pos < max; pos++) { + if (state.src.charCodeAt(pos) === 0x20) { return false; } + if (state.src.charCodeAt(pos) === 0x0A) { return false; } + if (state.src.charCodeAt(pos) === 0x5D /* ] */) { + break; + } + } + + if (pos === start + 2) { return false; } // no empty cite keys + if (pos >= max) { return false; } + + pos++; + + const key = state.src.slice(start + 2, pos - 1); + const citation = state.env.citations.find((entry) => entry.key === key); + + if (!silent) { + token = state.push('cite_ref', '', 0); + token.meta = { key, htmlKey: undefined !== citation ? citation.htmlKey : null }; + } + + state.pos = pos; + state.posMax = max; + + return true; + }); +}; diff --git a/app/scss/components/_preview.scss b/app/scss/components/_preview.scss index f40a7ef1..92b7dc3f 100644 --- a/app/scss/components/_preview.scss +++ b/app/scss/components/_preview.scss @@ -148,5 +148,15 @@ span > .task-list { margin-left: 0; } + + .citation { + font-family: Georgia, "Times New Roman", serif; + font-size: 1rem; + text-align: justify; + } + + .invalid-ref { + color: $warning-color; + } } } diff --git a/package.json b/package.json index b4ff7d04..ecbbe968 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "babel-polyfill": "^6.13.0", "babel-preset-airbnb": "^2.0.0", "babel-register": "^6.8.0", + "bibtex-parse-js": "0.0.23", "chai": "^3.5.0", "chai-as-promised": "^5.3.0", "clean-webpack-plugin": "^0.1.8", From 806a0d8aa74d81959617a1683b8aa56e38966c4b Mon Sep 17 00:00:00 2001 From: William Durand Date: Sun, 21 Aug 2016 18:18:41 +0200 Subject: [PATCH 2/7] Add tests for the cite md-it plugin and fix a few edge cases --- app/__tests__/bibtex-test.js | 66 +++++++----- app/bibtex.js | 40 ++++--- .../__tests__/cite-test.js | 14 +++ .../__tests__/fixtures/cite.txt | 102 ++++++++++++++++++ app/markdown-it-plugins/cite.js | 65 +++++++---- package.json | 1 + 6 files changed, 223 insertions(+), 65 deletions(-) create mode 100644 app/markdown-it-plugins/__tests__/cite-test.js create mode 100644 app/markdown-it-plugins/__tests__/fixtures/cite.txt diff --git a/app/__tests__/bibtex-test.js b/app/__tests__/bibtex-test.js index 34200cfd..3bcf7075 100644 --- a/app/__tests__/bibtex-test.js +++ b/app/__tests__/bibtex-test.js @@ -59,32 +59,10 @@ describe('bibtex', () => { }); }); - describe('getFirstAuthor()', () => { - it('should return the first author', () => { - const result = bibtex.getFirstAuthor('William Durand and Sebastien Salva'); - - expect(result).to.eql({ fullname: 'Durand, W.', lastname: 'Durand' }); - }); - - it('should return the first author', () => { - const authors = 'Victoria J. Hodge'; - const result = bibtex.getFirstAuthor(authors); - - expect(result).to.eql({ fullname: 'Hodge, V. J.', lastname: 'Hodge' }); - }); - - it('should return the first author', () => { - const authors = 'Hunter, Sarah and Corbett, Matthew'; - const result = bibtex.getFirstAuthor(authors); - - expect(result).to.eql({ fullname: 'Hunter, S.', lastname: 'Hunter' }); - }); - }); - describe('parse()', () => { it('should parse inproceedings', () => { const [ content, expected ] = fixture('inproceedings'); - const result = (new Bibtex()).parse(content); + const result = bibtex.parse(content); expect(result).to.have.length(1); expect(result[0].html).to.equal(expected); @@ -92,7 +70,7 @@ describe('bibtex', () => { it('should parse articles', () => { const [ content, expected ] = fixture('article'); - const result = (new Bibtex()).parse(content); + const result = bibtex.parse(content); expect(result).to.have.length(1); expect(result[0].html).to.equal(expected); @@ -100,7 +78,7 @@ describe('bibtex', () => { it('should parse multiple bibtex entries', () => { const [ content, expected ] = fixture('multi-entries'); - const result = (new Bibtex()).parse(content); + const result = bibtex.parse(content); expect(result).to.have.length(2); expect(result.map((e) => e.html).join('\n\n')).to.equal(expected); @@ -108,7 +86,7 @@ describe('bibtex', () => { it('should parse books', () => { const [ content, expected ] = fixture('book'); - const result = (new Bibtex()).parse(content); + const result = bibtex.parse(content); expect(result).to.have.length(1); expect(result[0].html).to.equal(expected); @@ -116,7 +94,7 @@ describe('bibtex', () => { it('should parse misc (fallback)', () => { const [ content, expected ] = fixture('misc'); - const result = (new Bibtex()).parse(content); + const result = bibtex.parse(content); expect(result).to.have.length(1); expect(result[0].html).to.equal(expected); @@ -124,10 +102,42 @@ describe('bibtex', () => { it('should return the same entry exactly once', () => { const [ content, expected ] = fixture('multi-same'); - const result = (new Bibtex()).parse(content); + const result = bibtex.parse(content); expect(result).to.have.length(2); expect(result.map((e) => e.html).join('\n\n')).to.equal(expected); }); }); + + describe('formatHtmlKey()', () => { + const year = 2010; + + it('should format the citation key for one author', () => { + const authors = 'Victoria J. Hodge'; + const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); + + expect(result).to.equal('Hodge, 2010'); + }); + + it('should format the citation key for one author (2)', () => { + const authors = 'William Durand'; + const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); + + expect(result).to.equal('Durand, 2010'); + }); + + it('should format the citation key for two authors', () => { + const authors = 'William Durand and Sebastien Salva'; + const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); + + expect(result).to.equal('Durand & Salva, 2010'); + }); + + it('should format the citation key for more than two authors', () => { + const authors = 'Hunter, Sarah and Corbett, Matthew and Denise, Hubert'; + const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); + + expect(result).to.equal('Hunter et al., 2010'); + }); + }); }); diff --git a/app/bibtex.js b/app/bibtex.js index 062079d0..c7260094 100644 --- a/app/bibtex.js +++ b/app/bibtex.js @@ -11,27 +11,35 @@ export default class Bibtex { for (let i = 0; i < entries.length; i++) { const e = entries[i]; const key = e.citationKey; - const firstAuthor = this.getFirstAuthor(e.entryTags.author); + + const authors = this.normalizeAuthors(e.entryTags.author); + const year = parseInt(e.entryTags.year, 10); if (undefined === keys.find((k) => key === k)) { keys.push(key); results.push({ key, - firstAuthor, + year, + authors, html: this.formatHtmlEntry(e.entryType, e.entryTags), - htmlKey: this.formatHtmlKey(firstAuthor, e.entryTags.year), + htmlKey: this.formatHtmlKey(authors, year), }); } } - return results - .sort((e1, e2) => - e1.firstAuthor.lastname.localeCompare(e2.firstAuthor.lastname) - ) - .map((e) => ( - { key: e.key, html: e.html, htmlKey: e.htmlKey } - )); + return results.sort(this.sortCitations); + } + + // first author's latname, then year + sortCitations(e1, e2) { + let r = e1.authors[0].lastname.localeCompare(e2.authors[0].lastname); + + if (0 === r) { + r = e1.year < e2.year ? -1 : 1; + } + + return r; } formatHtmlEntry(type, data) { @@ -76,12 +84,16 @@ export default class Bibtex { return this.replaceLaTeXChars(content); } - getFirstAuthor(authors) { - return this.normalizeAuthors(authors)[0]; + formatHtmlKey(authors, year) { + if (3 > authors.length) { + return `${authors.map((a) => a.lastname).join(' & ')}, ${year}`; + } + + return `${authors[0].lastname} et al., ${year}`; } - formatHtmlKey(firstAuthor, year) { - return `(${firstAuthor.lastname} et al., ${year})`; + formatHtmlKeys(citations) { + return `(${citations.map((c) => c.htmlKey).join('; ')})`; } normalizeAuthors(authors) { diff --git a/app/markdown-it-plugins/__tests__/cite-test.js b/app/markdown-it-plugins/__tests__/cite-test.js new file mode 100644 index 00000000..bdb79ab3 --- /dev/null +++ b/app/markdown-it-plugins/__tests__/cite-test.js @@ -0,0 +1,14 @@ +import path from 'path'; +import generate from 'markdown-it-testgen'; +import markdownIt from 'markdown-it'; +import cite from '../cite'; + +// see: https://github.com/mochajs/mocha/issues/1847 +const { describe } = global; + + +describe('markdown-it-plugins/cite', () => { + const mdIt = markdownIt().use(cite); + + generate(path.join(__dirname, 'fixtures/cite.txt'), { header: true }, mdIt); +}); diff --git a/app/markdown-it-plugins/__tests__/fixtures/cite.txt b/app/markdown-it-plugins/__tests__/fixtures/cite.txt new file mode 100644 index 00000000..08cba1af --- /dev/null +++ b/app/markdown-it-plugins/__tests__/fixtures/cite.txt @@ -0,0 +1,102 @@ +should parse block with `cite` +. +```cite +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +``` +. +

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf

+. + + +should render an "invalid" citation reference if not found +. +[@li_fast_2009] +. +

[@li_fast_2009]

+. + + +should render a well-formatted citation reference when found +. +According to [@logicreasoning] [...] + +## References + +```cite +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +``` +. +

According to (Hebert & Lewicki, 2007) [...]

+

References

+

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf

+. + +should not have problem with where the cite block is placed +. +```cite +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +``` + +According to [@logicreasoning] [...] +. +

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf

+

According to (Hebert & Lewicki, 2007) [...]

+. + +should indicate when at least on citation reference is invalid +. +According to [@logicreasoning;@invalid_ref] [...] + +```cite +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +``` +. +

According to [@logicreasoning;@invalid_ref] [...]

+

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf

+. + +should not parse empty references +. +[@] +. +

[@]

+. + +should parse block with `cite` even if there are extra spaces +. +``` cite +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +``` +. +

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf

+. diff --git a/app/markdown-it-plugins/cite.js b/app/markdown-it-plugins/cite.js index 7527a795..62333971 100644 --- a/app/markdown-it-plugins/cite.js +++ b/app/markdown-it-plugins/cite.js @@ -3,19 +3,24 @@ import Bibtex from '../bibtex'; module.exports = (md) => { - const defaultRender = md.renderer.rules.fence; + const MULTI_REFS_RE = /;@/; + const bibtex = new Bibtex(); + const defaultRender = md.renderer.rules.fence; // override fence renderer for `cite` md.renderer.rules.fence = (tokens, idx, options, env, self) => { const token = tokens[idx]; - if ('cite' === token.info) { - const entries = bibtex.parse(md.utils.escapeHtml(token.content)); + if (-1 !== token.info.indexOf('cite') && token.id) { + const entries = env.citations[token.id]; - return entries.map( - (entry) => `

${entry.html}

` - ).join('\n'); + if (undefined !== entries) { + return entries.map( + (entry) => `

${entry.html}

` + ).join('\n') + .concat('\n'); + } } // pass token to default renderer. @@ -25,13 +30,18 @@ module.exports = (md) => { // renderer for cite references `[@citation_ref]` md.renderer.rules.cite_ref = (tokens, idx) => { // also take: options, env, self const token = tokens[idx]; - const key = token.meta.key; - - if (!token.meta.htmlKey || null === token.meta.htmlKey) { - return `[@${key}]`; + const keys = token.meta.keys; + const citations = token.meta.citations; + + if (keys.length !== citations.length) { + return [ + '', + `[@${token.meta.reference}]`, + '', + ].join(''); } - return token.meta.htmlKey; + return bibtex.formatHtmlKeys(citations); }; // override fence block parser @@ -121,14 +131,14 @@ module.exports = (md) => { token.markup = markup; token.map = [startLine, state.line]; - if (!state.env.citations) { - state.env.citations = []; - } + if (-1 !== token.info.indexOf('cite')) { + if (!state.env.citations) { + state.env.citations = []; + } - if ('cite' === token.info) { - state.env.citations = state.env.citations.concat( - bibtex.parse(md.utils.escapeHtml(token.content)) - ); + token.id = state.env.citations.length + 1; + + state.env.citations[token.id] = bibtex.parse(md.utils.escapeHtml(token.content)); } return true; @@ -141,10 +151,9 @@ module.exports = (md) => { const max = state.posMax; const start = state.pos; - // should be at least 4 chars - "[^x]" + // should be at least 4 chars - "[@x]" if (start + 3 > max) { return false; } - if (!state.env.citations) { return false; } if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } if (state.src.charCodeAt(start + 1) !== 0x40/* @ */) { return false; } @@ -161,12 +170,22 @@ module.exports = (md) => { pos++; - const key = state.src.slice(start + 2, pos - 1); - const citation = state.env.citations.find((entry) => entry.key === key); + const reference = state.src.slice(start + 2, pos - 1); + const keys = reference.split(MULTI_REFS_RE); // deal with multiple refs + + let citations = []; + if (state.env.citations && 0 < state.env.citations.length) { + citations = state.env + .citations + .reduce((e1, e2) => e1.concat(e2)) + .filter(e => -1 !== keys.indexOf(e.key)) + .sort(bibtex.sortCitations) + ; + } if (!silent) { token = state.push('cite_ref', '', 0); - token.meta = { key, htmlKey: undefined !== citation ? citation.htmlKey : null }; + token.meta = { reference, keys, citations }; } state.pos = pos; diff --git a/package.json b/package.json index ecbbe968..ecb4043a 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", "markdown-it-task-lists": "^1.4.1", + "markdown-it-testgen": "^0.1.4", "mocha": "^2.4.5", "mocha-circleci-reporter": "0.0.2", "node-sass": "^3.4.2", From 9cd65c1636acddf7d9987677ae5bb70b1178298f Mon Sep 17 00:00:00 2001 From: William Durand Date: Mon, 22 Aug 2016 11:51:07 +0200 Subject: [PATCH 3/7] Transform bibtex into a lib --- app/__tests__/bibtex-test.js | 76 +++-- .../bib/multi-same-authors-diff-dates.bib | 41 +++ app/__tests__/fixtures/bib/no-authors.bib | 8 + app/bibtex.js | 295 ++++++++++-------- app/markdown-it-plugins/cite.js | 6 +- 5 files changed, 259 insertions(+), 167 deletions(-) create mode 100644 app/__tests__/fixtures/bib/multi-same-authors-diff-dates.bib create mode 100644 app/__tests__/fixtures/bib/no-authors.bib diff --git a/app/__tests__/bibtex-test.js b/app/__tests__/bibtex-test.js index 3bcf7075..937498b0 100644 --- a/app/__tests__/bibtex-test.js +++ b/app/__tests__/bibtex-test.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import path from 'path'; import fs from 'fs'; -import Bibtex from '../bibtex'; +import bibtex from '../bibtex'; // see: https://github.com/mochajs/mocha/issues/1847 const { describe, it } = global; @@ -20,8 +20,6 @@ function fixture(name) { } describe('bibtex', () => { - const bibtex = new Bibtex(); - describe('normalizeAuthors()', () => { it('should normalize an author without comma and multiple names', () => { const authors = 'Victoria J. Hodge'; @@ -57,11 +55,35 @@ describe('bibtex', () => { expect(result[1]).to.eql({ fullname: 'Corbett, M.', lastname: 'Corbett' }); expect(result[2]).to.eql({ fullname: 'Denise, H.', lastname: 'Denise' }); }); + + it('should deal with empty authors', () => { + const authors = ''; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(0); + }); + + it('should normalize only a lastname', () => { + const authors = 'Durand'; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(1); + expect(result[0]).to.eql({ fullname: 'Durand', lastname: 'Durand' }); + }); + + it('should normalize only lastnames', () => { + const authors = 'Durand and Salva'; + const result = bibtex.normalizeAuthors(authors); + + expect(result).to.have.length(2); + expect(result[0]).to.eql({ fullname: 'Durand', lastname: 'Durand' }); + expect(result[1]).to.eql({ fullname: 'Salva', lastname: 'Salva' }); + }); }); describe('parse()', () => { it('should parse inproceedings', () => { - const [ content, expected ] = fixture('inproceedings'); + const [content, expected] = fixture('inproceedings'); const result = bibtex.parse(content); expect(result).to.have.length(1); @@ -69,7 +91,7 @@ describe('bibtex', () => { }); it('should parse articles', () => { - const [ content, expected ] = fixture('article'); + const [content, expected] = fixture('article'); const result = bibtex.parse(content); expect(result).to.have.length(1); @@ -77,7 +99,7 @@ describe('bibtex', () => { }); it('should parse multiple bibtex entries', () => { - const [ content, expected ] = fixture('multi-entries'); + const [content, expected] = fixture('multi-entries'); const result = bibtex.parse(content); expect(result).to.have.length(2); @@ -85,7 +107,7 @@ describe('bibtex', () => { }); it('should parse books', () => { - const [ content, expected ] = fixture('book'); + const [content, expected] = fixture('book'); const result = bibtex.parse(content); expect(result).to.have.length(1); @@ -93,7 +115,7 @@ describe('bibtex', () => { }); it('should parse misc (fallback)', () => { - const [ content, expected ] = fixture('misc'); + const [content, expected] = fixture('misc'); const result = bibtex.parse(content); expect(result).to.have.length(1); @@ -101,7 +123,7 @@ describe('bibtex', () => { }); it('should return the same entry exactly once', () => { - const [ content, expected ] = fixture('multi-same'); + const [content, expected] = fixture('multi-same'); const result = bibtex.parse(content); expect(result).to.have.length(2); @@ -109,35 +131,27 @@ describe('bibtex', () => { }); }); - describe('formatHtmlKey()', () => { - const year = 2010; + describe('html.renderReferences()', () => { + it('should format the keys of a set of citations', () => { + const [content, _] = fixture('multi-entries'); + const result = bibtex.html.renderReferences(bibtex.parse(content)); - it('should format the citation key for one author', () => { - const authors = 'Victoria J. Hodge'; - const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); - - expect(result).to.equal('Hodge, 2010'); + expect(result).to.equal('(Durand & Salva, 2015; Hodge & Austin, 2004)'); }); - it('should format the citation key for one author (2)', () => { - const authors = 'William Durand'; - const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); + it('should deal with empty authors', () => { + const [content, _] = fixture('no-authors'); + const result = bibtex.html.renderReferences(bibtex.parse(content)); - expect(result).to.equal('Durand, 2010'); + expect(result).to.equal('(Unknown authors, 2007)'); }); - it('should format the citation key for two authors', () => { - const authors = 'William Durand and Sebastien Salva'; - const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); - - expect(result).to.equal('Durand & Salva, 2010'); - }); - - it('should format the citation key for more than two authors', () => { - const authors = 'Hunter, Sarah and Corbett, Matthew and Denise, Hubert'; - const result = bibtex.formatHtmlKey(bibtex.normalizeAuthors(authors), year); + // TODO: implement this feature + it.skip('should handle same authors with different dates', () => { + const [content, _] = fixture('multi-same-authors-diff-dates'); + const result = bibtex.html.renderReferences(bibtex.parse(content)); - expect(result).to.equal('Hunter et al., 2010'); + expect(result).to.equal('(Li & Durbin, 2009, 2010)'); }); }); }); diff --git a/app/__tests__/fixtures/bib/multi-same-authors-diff-dates.bib b/app/__tests__/fixtures/bib/multi-same-authors-diff-dates.bib new file mode 100644 index 00000000..16f693ad --- /dev/null +++ b/app/__tests__/fixtures/bib/multi-same-authors-diff-dates.bib @@ -0,0 +1,41 @@ +@article{li_fast_2010, + title = {Fast and accurate long-read alignment with {Burrows}–{Wheeler} transform}, + volume = {26}, + issn = {1367-4803, 1460-2059}, + url = {http://bioinformatics.oxfordjournals.org/content/26/5/589}, + doi = {10.1093/bioinformatics/btp698}, + abstract = {Motivation: Many programs for aligning short sequencing reads to a reference genome have been developed in the last 2 years. Most of them are very efficient for short reads but inefficient or not applicable for reads {\textgreater}200 bp because the algorithms are heavily and specifically tuned for short queries with low sequencing error rate. However, some sequencing platforms already produce longer reads and others are expected to become available soon. For longer reads, hashing-based software such as BLAT and SSAHA2 remain the only choices. Nonetheless, these methods are substantially slower than short-read aligners in terms of aligned bases per unit time. + Results: We designed and implemented a new algorithm, Burrows-Wheeler Aligner's Smith-Waterman Alignment (BWA-SW), to align long sequences up to 1 Mb against a large sequence database (e.g. the human genome) with a few gigabytes of memory. The algorithm is as accurate as SSAHA2, more accurate than BLAT, and is several to tens of times faster than both. + Availability: http://bio-bwa.sourceforge.net + Contact: rd@sanger.ac.uk}, + language = {en}, + number = {5}, + urldate = {2016-05-12}, + journal = {Bioinformatics}, + author = {Li, Heng and Durbin, Richard}, + month = mar, + year = {2010}, + pmid = {20080505}, + pages = {589--595} +} + +@article{li_fast_2009, + title = {Fast and accurate short read alignment with {Burrows}-{Wheeler} transform}, + volume = {25}, + issn = {1367-4811}, + doi = {10.1093/bioinformatics/btp324}, + abstract = {MOTIVATION: The enormous amount of short reads generated by the new DNA sequencing technologies call for the development of fast and accurate read alignment programs. A first generation of hash table-based methods has been developed, including MAQ, which is accurate, feature rich and fast enough to align short reads from a single individual. However, MAQ does not support gapped alignment for single-end reads, which makes it unsuitable for alignment of longer reads where indels may occur frequently. The speed of MAQ is also a concern when the alignment is scaled up to the resequencing of hundreds of individuals. + RESULTS: We implemented Burrows-Wheeler Alignment tool (BWA), a new read alignment package that is based on backward search with Burrows-Wheeler Transform (BWT), to efficiently align short sequencing reads against a large reference sequence such as the human genome, allowing mismatches and gaps. BWA supports both base space reads, e.g. from Illumina sequencing machines, and color space reads from AB SOLiD machines. Evaluations on both simulated and real data suggest that BWA is approximately 10-20x faster than MAQ, while achieving similar accuracy. In addition, BWA outputs alignment in the new standard SAM (Sequence Alignment/Map) format. Variant calling and other downstream analyses after the alignment can be achieved with the open source SAMtools software package. + AVAILABILITY: http://maq.sourceforge.net.}, + language = {eng}, + number = {14}, + journal = {Bioinformatics (Oxford, England)}, + author = {Li, Heng and Durbin, Richard}, + month = jul, + year = {2009}, + pmid = {19451168}, + pmcid = {PMC2705234}, + keywords = {Algorithms, Genomics, Sequence Alignment, Sequence Analysis, DNA, software}, + pages = {1754--1760} +} +--- EXPECTED --- diff --git a/app/__tests__/fixtures/bib/no-authors.bib b/app/__tests__/fixtures/bib/no-authors.bib new file mode 100644 index 00000000..dda67521 --- /dev/null +++ b/app/__tests__/fixtures/bib/no-authors.bib @@ -0,0 +1,8 @@ +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={}, + note={Lecture. Available from + https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf}, + year={2007} +} +--- EXPECTED --- diff --git a/app/bibtex.js b/app/bibtex.js index c7260094..21e331ec 100644 --- a/app/bibtex.js +++ b/app/bibtex.js @@ -1,107 +1,70 @@ import parser from 'bibtex-parse-js'; -export default class Bibtex { - - parse(bibtex) { - const entries = parser.toJSON(bibtex); - - const keys = []; - const results = []; - for (let i = 0; i < entries.length; i++) { - const e = entries[i]; - const key = e.citationKey; - - const authors = this.normalizeAuthors(e.entryTags.author); - const year = parseInt(e.entryTags.year, 10); - - if (undefined === keys.find((k) => key === k)) { - keys.push(key); - - results.push({ - key, - year, - authors, - html: this.formatHtmlEntry(e.entryType, e.entryTags), - htmlKey: this.formatHtmlKey(authors, year), - }); - } - } +// constants +const MAX_AUTHORS_IN_REFS = 2; +const MAX_AUTHORS_IN_CITATIONS = 6; - return results.sort(this.sortCitations); - } +// utils - // first author's latname, then year - sortCitations(e1, e2) { - let r = e1.authors[0].lastname.localeCompare(e2.authors[0].lastname); +function optional(string, fallback) { + const str = undefined === string ? fallback : string; - if (0 === r) { - r = e1.year < e2.year ? -1 : 1; - } + return undefined !== str ? `(${str})` : ''; +} - return r; +function linkify(string) { + if (/^https?:\/\//.test(string)) { + return `${string}`; } - formatHtmlEntry(type, data) { - let content; - switch (type.toLowerCase()) { - case 'inproceedings': - content = [ - `${this.formatAuthors(data.author)} (${data.year}). ${data.title}.`, - `In ${data.booktitle} (pp. ${data.pages}). ${this.urlize(data.url)}`, - ].join(' '); - break; - - case 'article': - content = [ - `${this.formatAuthors(data.author)} (${data.year}).`, - `${data.title}.`, - `${data.journal},`, - `${data.volume}${this.optional(data.issue, data.number)},`, - `${data.pages}.`, - `${this.urlizeDOI(data.doi)}`, - ].join(' '); - break; - - case 'book': - content = [ - `${this.formatAuthors(data.author)} (${data.year}).`, - `${data.title}.`, - `${data.publisher}`, - ].join(' '); - break; - - case 'misc': - default: - content = [ - `${this.formatAuthors(data.author)} (${data.year}).`, - `${data.title}.`, - `${data.note || ''}`, - ].join(' '); - - } + return string; +} - return this.replaceLaTeXChars(content); +function linkifyDOI(string) { + let str = string; + if (!/^https?:\/\//.test(str)) { + str = `http://dx.doi.org/${str}`; } - formatHtmlKey(authors, year) { - if (3 > authors.length) { - return `${authors.map((a) => a.lastname).join(' & ')}, ${year}`; - } + return linkify(str); +} - return `${authors[0].lastname} et al., ${year}`; - } +function replaceLaTeXChars(string) { + // TODO: need more replacements + return string + .replace(/\\'e/g, 'é') + .replace(/\\'\{e\}/g, 'é') + .replace(/\{/g, '') + .replace(/\}/g, '') + .replace(/--/g, '—') + .replace(/\s+/g, ' ') + ; +} + +// sorting + +// first author's latname, then year +function sortCitations(e1, e2) { + let r = e1.authors[0].lastname.localeCompare(e2.authors[0].lastname); - formatHtmlKeys(citations) { - return `(${citations.map((c) => c.htmlKey).join('; ')})`; + if (0 === r) { + r = e1.year < e2.year ? -1 : 1; } - normalizeAuthors(authors) { - const parts = authors.split(/\sand\s/); + return r; +} - return parts.map((fullname) => { - let otherNames; +// formatters + +function normalizeAuthors(authors) { + const parts = authors.split(/\sand\s/); + + return parts + .filter(fullname => '' !== fullname) + .map((fullname) => { let lastname; + let otherNames; if (/,/.test(fullname)) { const subparts = fullname.split(/,/); lastname = subparts[0].trim(); @@ -122,71 +85,139 @@ export default class Bibtex { .join(' '); return { - fullname: `${lastname}, ${otherNames}`, + fullname: '' !== otherNames ? `${lastname}, ${otherNames}` : lastname, lastname, }; }); - } +} - formatAuthors(authors) { - const normalizedAuthors = this.normalizeAuthors(authors); - const truncate = 6 < normalizedAuthors.length; +function formatAuthors(authors) { + const normalizedAuthors = normalizeAuthors(authors); - let authorsToDisplay = normalizedAuthors; - if (truncate) { - authorsToDisplay = normalizedAuthors.splice(0, 6); - } + if (0 === normalizedAuthors.length) { + return ''; + } - // because we have to put `author, author, author & last author name` - const lastAuthorToDisplay = authorsToDisplay.pop(); + const truncate = MAX_AUTHORS_IN_CITATIONS < normalizedAuthors.length; - let authorsString = authorsToDisplay - .map((a) => a.fullname) - .join(', ') - .concat(` & ${lastAuthorToDisplay.fullname}`); + let authorsToDisplay = normalizedAuthors; + if (truncate) { + authorsToDisplay = normalizedAuthors.splice(0, MAX_AUTHORS_IN_CITATIONS); + } - if (truncate) { - authorsString += ' et al.'; - } + // because we have to put `author, author, author & last author name` + const lastAuthorToDisplay = authorsToDisplay.pop(); - return this.replaceLaTeXChars(authorsString); + let authorsString = authorsToDisplay + .map((a) => a.fullname) + .join(', ') + .concat(` & ${lastAuthorToDisplay.fullname}`); + + if (truncate) { + authorsString += ' et al.'; } - optional(string, fallback) { - let str = string; + return replaceLaTeXChars(authorsString); +} - if (undefined === str) { - str = fallback; - } +function formatHtmlEntry(type, data) { + let content; + switch (type.toLowerCase()) { + case 'inproceedings': + content = [ + `${formatAuthors(data.author)} (${data.year}). ${data.title}.`, + `In ${data.booktitle} (pp. ${data.pages}). ${linkify(data.url)}`, + ].join(' '); + break; + + case 'article': + content = [ + `${formatAuthors(data.author)} (${data.year}).`, + `${data.title}.`, + `${data.journal},`, + `${data.volume}${optional(data.issue, data.number)},`, + `${data.pages}.`, + `${linkifyDOI(data.doi)}`, + ].join(' '); + break; + + case 'book': + content = [ + `${formatAuthors(data.author)} (${data.year}).`, + `${data.title}.`, + `${data.publisher}`, + ].join(' '); + break; + + case 'misc': + default: + content = [ + `${formatAuthors(data.author)} (${data.year}).`, + `${data.title}.`, + `${data.note || ''}`, + ].join(' '); - return undefined !== str ? `(${str})` : ''; } - urlizeDOI(string) { - let str = string; - if (!/^https?:\/\//.test(str)) { - str = `http://dx.doi.org/${str}`; - } + return replaceLaTeXChars(content); +} - return this.urlize(str); +function formatHtmlKey(authors, year) { + if (0 === authors.length) { + return `Unknown authors, ${year}`; } - urlize(string) { - if (/^https?:\/\//.test(string)) { - return `${string}`; - } - - return string; + if (MAX_AUTHORS_IN_REFS >= authors.length) { + return `${authors.map((a) => a.lastname).join(' & ')}, ${year}`; } - replaceLaTeXChars(string) { - return string - .replace(/\\'e/g, 'é') - .replace(/\\'\{e\}/g, 'é') - .replace(/\{/g, '') - .replace(/\}/g, '') - .replace(/--/g, '—') - .replace(/\s+/g, ' ') - ; + return `${authors[0].lastname} et al., ${year}`; +} + +function formatHtmlReferences(citations) { + return `(${citations.map((c) => c.htmlKey).join('; ')})`; +} + +// parsing + +function parse(bibtex) { + const entries = parser.toJSON(bibtex); + + const keys = []; + const results = []; + for (let i = 0; i < entries.length; i++) { + const e = entries[i]; + const key = e.citationKey; + + const authors = normalizeAuthors(e.entryTags.author); + const year = parseInt(e.entryTags.year, 10); + + if (undefined === keys.find((k) => key === k)) { + keys.push(key); + + results.push({ + key, + year, + authors, + html: formatHtmlEntry(e.entryType, e.entryTags), + htmlKey: formatHtmlKey(authors, year), + }); + } } + + return results.sort(sortCitations); } + +const bibtex = { + // returns a set of citations + parse, + // returns a set of normalized authors + normalizeAuthors, + // HTML renderer + html: { + // returns well-formatted references given a set of citations + renderReferences: formatHtmlReferences, + }, +}; + +export default bibtex; diff --git a/app/markdown-it-plugins/cite.js b/app/markdown-it-plugins/cite.js index 62333971..911660c7 100644 --- a/app/markdown-it-plugins/cite.js +++ b/app/markdown-it-plugins/cite.js @@ -1,11 +1,9 @@ /* eslint no-param-reassign: 0, yoda: 0, no-continue: 0 */ -import Bibtex from '../bibtex'; +import bibtex from '../bibtex'; module.exports = (md) => { const MULTI_REFS_RE = /;@/; - - const bibtex = new Bibtex(); const defaultRender = md.renderer.rules.fence; // override fence renderer for `cite` @@ -41,7 +39,7 @@ module.exports = (md) => { ].join(''); } - return bibtex.formatHtmlKeys(citations); + return bibtex.html.renderReferences(citations); }; // override fence block parser From c9d276cc773f5bb6e6c6e9439b5624dc755288f5 Mon Sep 17 00:00:00 2001 From: William Durand Date: Mon, 22 Aug 2016 12:07:39 +0200 Subject: [PATCH 4/7] Do not change font for citations --- app/scss/components/_preview.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scss/components/_preview.scss b/app/scss/components/_preview.scss index 92b7dc3f..7c518c3d 100644 --- a/app/scss/components/_preview.scss +++ b/app/scss/components/_preview.scss @@ -150,7 +150,6 @@ } .citation { - font-family: Georgia, "Times New Roman", serif; font-size: 1rem; text-align: justify; } From b78edbb0f0ef078a48306e9de9cf58cca0bdeb3e Mon Sep 17 00:00:00 2001 From: William Durand Date: Mon, 22 Aug 2016 12:08:08 +0200 Subject: [PATCH 5/7] Move HTML into the bibtex lib Don't know if it is a good idea, but at least we can make it testable... --- app/bibtex.js | 20 ++++++++++++++++++++ app/markdown-it-plugins/cite.js | 15 ++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/bibtex.js b/app/bibtex.js index 21e331ec..4d98fcea 100644 --- a/app/bibtex.js +++ b/app/bibtex.js @@ -178,6 +178,22 @@ function formatHtmlReferences(citations) { return `(${citations.map((c) => c.htmlKey).join('; ')})`; } +function formatHtmlCitations(citations) { + return citations + .map(c => `

${c.html}

`) + .join('\n') + .concat('\n') + ; +} + +function formatHtmlInvalidReferences(references) { + return [ + '', + references, + '', + ].join(''); +} + // parsing function parse(bibtex) { @@ -217,6 +233,10 @@ const bibtex = { html: { // returns well-formatted references given a set of citations renderReferences: formatHtmlReferences, + // returns HTML for to highlight an invalid reference (given as a string) + renderInvalidReferences: formatHtmlInvalidReferences, + // returns well-formatted citations + renderCitations: formatHtmlCitations, }, }; diff --git a/app/markdown-it-plugins/cite.js b/app/markdown-it-plugins/cite.js index 911660c7..31cb8777 100644 --- a/app/markdown-it-plugins/cite.js +++ b/app/markdown-it-plugins/cite.js @@ -11,13 +11,10 @@ module.exports = (md) => { const token = tokens[idx]; if (-1 !== token.info.indexOf('cite') && token.id) { - const entries = env.citations[token.id]; + const citations = env.citations[token.id]; - if (undefined !== entries) { - return entries.map( - (entry) => `

${entry.html}

` - ).join('\n') - .concat('\n'); + if (undefined !== citations) { + return bibtex.html.renderCitations(citations); } } @@ -32,11 +29,7 @@ module.exports = (md) => { const citations = token.meta.citations; if (keys.length !== citations.length) { - return [ - '', - `[@${token.meta.reference}]`, - '', - ].join(''); + return bibtex.html.renderInvalidReferences(`[@${token.meta.reference}]`); } return bibtex.html.renderReferences(citations); From 646d8ce4d6c81852427d5eacfe5704b10198e472 Mon Sep 17 00:00:00 2001 From: William Durand Date: Mon, 22 Aug 2016 12:47:06 +0200 Subject: [PATCH 6/7] Add some more tests --- app/__tests__/bibtex-test.js | 8 ++++ app/__tests__/fixtures/bib/multi-unsorted.bib | 44 +++++++++++++++++++ .../__tests__/fixtures/cite.txt | 39 ++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 app/__tests__/fixtures/bib/multi-unsorted.bib diff --git a/app/__tests__/bibtex-test.js b/app/__tests__/bibtex-test.js index 937498b0..d47a30c7 100644 --- a/app/__tests__/bibtex-test.js +++ b/app/__tests__/bibtex-test.js @@ -129,6 +129,14 @@ describe('bibtex', () => { expect(result).to.have.length(2); expect(result.map((e) => e.html).join('\n\n')).to.equal(expected); }); + + it('should sort the entries', () => { + const [content, expected] = fixture('multi-unsorted'); + const result = bibtex.parse(content); + + expect(result).to.have.length(2); + expect(result.map((e) => e.html).join('\n\n')).to.equal(expected); + }); }); describe('html.renderReferences()', () => { diff --git a/app/__tests__/fixtures/bib/multi-unsorted.bib b/app/__tests__/fixtures/bib/multi-unsorted.bib new file mode 100644 index 00000000..24c272aa --- /dev/null +++ b/app/__tests__/fixtures/bib/multi-unsorted.bib @@ -0,0 +1,44 @@ +@article{li_fast_2010, + title = {Fast and accurate long-read alignment with {Burrows}–{Wheeler} transform}, + volume = {26}, + issn = {1367-4803, 1460-2059}, + url = {http://bioinformatics.oxfordjournals.org/content/26/5/589}, + doi = {10.1093/bioinformatics/btp698}, + abstract = {Motivation: Many programs for aligning short sequencing reads to a reference genome have been developed in the last 2 years. Most of them are very efficient for short reads but inefficient or not applicable for reads {\textgreater}200 bp because the algorithms are heavily and specifically tuned for short queries with low sequencing error rate. However, some sequencing platforms already produce longer reads and others are expected to become available soon. For longer reads, hashing-based software such as BLAT and SSAHA2 remain the only choices. Nonetheless, these methods are substantially slower than short-read aligners in terms of aligned bases per unit time. + Results: We designed and implemented a new algorithm, Burrows-Wheeler Aligner's Smith-Waterman Alignment (BWA-SW), to align long sequences up to 1 Mb against a large sequence database (e.g. the human genome) with a few gigabytes of memory. The algorithm is as accurate as SSAHA2, more accurate than BLAT, and is several to tens of times faster than both. + Availability: http://bio-bwa.sourceforge.net + Contact: rd@sanger.ac.uk}, + language = {en}, + number = {5}, + urldate = {2016-05-12}, + journal = {Bioinformatics}, + author = {Li, Heng and Durbin, Richard}, + month = mar, + year = {2010}, + pmid = {20080505}, + pages = {589--595} +} + +@article{li_fast_2009, + title = {Fast and accurate short read alignment with {Burrows}-{Wheeler} transform}, + volume = {25}, + issn = {1367-4811}, + doi = {10.1093/bioinformatics/btp324}, + abstract = {MOTIVATION: The enormous amount of short reads generated by the new DNA sequencing technologies call for the development of fast and accurate read alignment programs. A first generation of hash table-based methods has been developed, including MAQ, which is accurate, feature rich and fast enough to align short reads from a single individual. However, MAQ does not support gapped alignment for single-end reads, which makes it unsuitable for alignment of longer reads where indels may occur frequently. The speed of MAQ is also a concern when the alignment is scaled up to the resequencing of hundreds of individuals. + RESULTS: We implemented Burrows-Wheeler Alignment tool (BWA), a new read alignment package that is based on backward search with Burrows-Wheeler Transform (BWT), to efficiently align short sequencing reads against a large reference sequence such as the human genome, allowing mismatches and gaps. BWA supports both base space reads, e.g. from Illumina sequencing machines, and color space reads from AB SOLiD machines. Evaluations on both simulated and real data suggest that BWA is approximately 10-20x faster than MAQ, while achieving similar accuracy. In addition, BWA outputs alignment in the new standard SAM (Sequence Alignment/Map) format. Variant calling and other downstream analyses after the alignment can be achieved with the open source SAMtools software package. + AVAILABILITY: http://maq.sourceforge.net.}, + language = {eng}, + number = {14}, + journal = {Bioinformatics (Oxford, England)}, + author = {Li, Heng and Durbin, Richard}, + month = jul, + year = {2009}, + pmid = {19451168}, + pmcid = {PMC2705234}, + keywords = {Algorithms, Genomics, Sequence Alignment, Sequence Analysis, DNA, software}, + pages = {1754--1760} +} +--- EXPECTED --- +Li, H. & Durbin, R. (2009). Fast and accurate short read alignment with Burrows-Wheeler transform. Bioinformatics (Oxford, England), 25(14), 1754—1760. http://dx.doi.org/10.1093/bioinformatics/btp324 + +Li, H. & Durbin, R. (2010). Fast and accurate long-read alignment with Burrows–Wheeler transform. Bioinformatics, 26(5), 589—595. http://dx.doi.org/10.1093/bioinformatics/btp698 diff --git a/app/markdown-it-plugins/__tests__/fixtures/cite.txt b/app/markdown-it-plugins/__tests__/fixtures/cite.txt index 08cba1af..45991c66 100644 --- a/app/markdown-it-plugins/__tests__/fixtures/cite.txt +++ b/app/markdown-it-plugins/__tests__/fixtures/cite.txt @@ -100,3 +100,42 @@ should parse block with `cite` even if there are extra spaces .

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning. Lecture. Available from https://www.cs.cmu.edu/afs/cs/academic/class/15381-s07/www/slides/022707reasoning.pdf

. + +should parse and render other fence blocks +. +``` php +<?php + +echo 'Hello'; + +. + +should deal with multiple `cite` blocks +. +As in [@logicreasoning;@another] ... + +```cite +@misc{logicreasoning, + title={Artificial Intelligence, Logic and Reasoning}, + author={Martial Hebert and Mike Lewicki}, + year={2007} +} +``` + +``` cite +@misc{another, + title={Another Brick In The Wall}, + author={Pink Floyd}, + year={1979} +} +``` +. +

As in (Hebert & Lewicki, 2007; Floyd, 1979) ...

+

Hebert, M. & Lewicki, M. (2007). Artificial Intelligence, Logic and Reasoning.

+

& Floyd, P. (1979). Another Brick In The Wall.

+. From f89993e3ab7b9623bd57c6b3d6170ae541f6d9fe Mon Sep 17 00:00:00 2001 From: William Durand Date: Mon, 22 Aug 2016 16:27:55 +0200 Subject: [PATCH 7/7] Add doc --- doc/images/bibtex.png | Bin 0 -> 85343 bytes doc/writing.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 doc/images/bibtex.png diff --git a/doc/images/bibtex.png b/doc/images/bibtex.png new file mode 100644 index 0000000000000000000000000000000000000000..5476bdea673bc789b364dcfd68de43097e2f6d83 GIT binary patch literal 85343 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 Czy2ox literal 0 HcmV?d00001 diff --git a/doc/writing.md b/doc/writing.md index de845ce1..1cdd4735 100644 --- a/doc/writing.md +++ b/doc/writing.md @@ -22,6 +22,7 @@ extensions. * [Abbreviations](#abbreviations) * [Autolinks](#autolinks) +* [BibTeX support](#bibtex-support) * [Code](#code) * [Emoji](#emoji) * [highlighting](#highlighting) @@ -56,6 +57,39 @@ In regular Markdown, links are usually written with the `[link description](url)` syntax. In Monod, you can directly paste an URL in your document, and it will be automatically transformed into a link. +### BibTeX support + +Using [fenced code blocks](#code) (see the next section) à la GitHub, you can +add scientific references and citations into your Monod documents. Monod will +just render them nicely: + +![](images/bibtex.png) + +Use as many fenced `cite` code blocks as you wish to add citations, and use the +Pandoc `[@citation_ref]` syntax to add references to these citations (using the +citation "keys" or "references"): + + According to [@citation_ref], this is not accurate. + + ``` cite + @article{citation_ref, + ... + } + ``` + +In the screenshot above, the `[@logicreasoning]` reference points to the +citation in the fenced `cite` code block at the bottom of the document. Note +that you can put multiple `cite` blocks, and there is no specific position +(place them wherever you wish, but do not forget that they will be rendered +where you put them in the preview). It is recommended to put all your citations +in one single `cite` code block though (as you probably already do in your +`.bib` file). + +The `[@li_fast_2009;@li_fast_2010]` reference contains two references, and it is +marked as invalid because Monod does not find any citation that corresponds to +any of the given references. With multiple references, only one not found +reference will mark the whole reference as invalid. + ### Code Code can be rendered with fenced code blocks: