Skip to content

Commit

Permalink
citeproc-rs conversion: Export tools
Browse files Browse the repository at this point in the history
  • Loading branch information
tnajdek committed Jun 8, 2021
1 parent 151a884 commit d0b5ee2
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 53 deletions.
55 changes: 45 additions & 10 deletions src/js/cite.js
@@ -1,19 +1,21 @@
const metaCiteprocRStoJS = bibliographyMeta => ({
bibstart: bibliographyMeta.formatMeta.markupPre,
bibend: bibliographyMeta.formatMeta.markupPost,
hangingindent: bibliographyMeta.hangingIndent,
maxoffset: bibliographyMeta.maxOffset,
entryspacing: bibliographyMeta.entrySpacing,
linespacing: bibliographyMeta.lineSpacing,
'second-field-align': bibliographyMeta.secondFieldAlign
});

const formatFallback = bibliographyItems => {
return `<ol><li>${bibliographyItems.join('</li><li>')}</li></ol>`;
}

// adapter from citeproc-rs output
const formatBib = (bibliographyItems, bibliographyMeta) => {
return formatBibLegacy([
{
bibstart: bibliographyMeta.formatMeta.markupPre,
bibend: bibliographyMeta.formatMeta.markupPost,
hangingindent: bibliographyMeta.hangingIndent,
maxoffset: bibliographyMeta.maxOffset,
entryspacing: bibliographyMeta.entrySpacing,
linespacing: bibliographyMeta.lineSpacing,
'second-field-align': bibliographyMeta.secondFieldAlign
},
metaCiteprocRStoJS(bibliographyMeta),
bibliographyItems.map(renderedItem => (
`<div className="csl-entry">${renderedItem.value}</div>`
)).join('')
Expand Down Expand Up @@ -136,4 +138,37 @@ const formatBibLegacy = (bib) => {
return container.innerHTML;
};

export { formatBib, formatFallback };
const getBibliographyFormatParameters = bibliographyMeta =>
getBibliographyFormatParametersLegacy([metaCiteprocRStoJS(bibliographyMeta)]);

/**
* copied from https://github.com/zotero/zotero/blob/1f5639da4297ac20fd21223d2004a7cfeef72e21/chrome/content/zotero/xpcom/cite.js#L43
* Convert formatting data from citeproc-js bibliography object into explicit format
* parameters for RTF or word processors
* @param {bib} citeproc-js bibliography object
* @return {Object} Bibliography style parameters.
*/
const getBibliographyFormatParametersLegacy = bib => {
var bibStyle = {'tabStops':[], 'indent':0, 'firstLineIndent':0,
'lineSpacing':(240 * bib[0].linespacing),
'entrySpacing':(240 * bib[0].entryspacing)};
if(bib[0].hangingindent) {
bibStyle.indent = 720; // 720 twips = 0.5 in
bibStyle.firstLineIndent = -720; // -720 twips = -0.5 in
} else if(bib[0]['second-field-align']) {
// this is a really sticky issue. the below works for first fields that look like "[1]"
// and "1." otherwise, i have no idea. luckily, this will be good enough 99% of the time.
var alignAt = 24+bib[0].maxoffset*120;
bibStyle.firstLineIndent = -alignAt;
if(bib[0]['second-field-align'] == 'margin') {
bibStyle.tabStops = [0];
} else {
bibStyle.indent = alignAt;
bibStyle.tabStops = [alignAt];
}
}

return bibStyle;
};

export { formatBib, formatBibLegacy, formatFallback, getBibliographyFormatParameters, getBibliographyFormatParametersLegacy };
91 changes: 87 additions & 4 deletions src/js/components/container.jsx
Expand Up @@ -10,8 +10,10 @@ processMultipleChoiceItems, processSentenceCaseAPAItems, retrieveIndependentStyl
retrieveStylesData, saveToPermalink, validateItem, validateUrl } from '../utils';
import { coreCitationStyles } from '../../../data/citation-styles-data.json';
import defaults from '../constants/defaults';
import exportFormats from '../constants/export-formats';
import ZBib from './zbib';
import { useCitationStyle, usePrevious } from '../hooks';
import { formatBib, getBibliographyFormatParameters } from '../cite';

var msgId = 0;
const getNextMessageId = () => ++msgId < Number.MAX_SAFE_INTEGER ? msgId : (msgId = 0);
Expand All @@ -23,6 +25,7 @@ const BibWebContainer = props => {
const citeproc = useRef(null);
const bib = useRef(null);
const copyData = useRef(null);
const copyDataInclude = useRef(null);
const [isCiteprocReady, setIsCiteprocReady] = useState(false);
const [isDataReady, setIsDataReady] = useState(false);
const [activeDialog, setActiveDialog] = useState(null);
Expand Down Expand Up @@ -204,6 +207,78 @@ const BibWebContainer = props => {
}
}, [citationStyles, config, handleError, history, remoteId]);

const getCopyData = useCallback(async format => {
if(format === 'text') {
//NOTE: citeprocRS uses 'plain', citeprocJS uses 'text';
format = 'plain';
}
const { bibliographyItems, bibliographyMeta } = await getOneTimeBibliographyOrFallback(
bib.current.itemsCSL, citationStyleXml, styleHasBibliography, format
);

if(bibliographyItems) {
const copyData = format === 'html' ?
formatBib(bibliographyItems, bibliographyMeta) :
bibliographyItems.map(i => i.value).join('\n');

if(exportFormats[format].include) {
copyDataInclude.current = [
{
mime: exportFormats[format].mime,
data: copyData
},
{
mime: exportFormats[exportFormats[format].include].mime,
data: await getCopyData(exportFormats[format].include)
}];
}
return copyData;
}

return '';
}, [citationStyleXml, styleHasBibliography]);

const getFileData = useCallback(async format => {
var fileContents, separator, bibStyle, preamble = '';

if(format === 'ris') {
try {
fileContents = await bib.current.exportItems('ris');
} catch(e) {
handleError(e.message);
return;
}
} else if(format === 'bibtex') {
try {
fileContents = await bib.current.exportItems('bibtex');
} catch(e) {
handleError(e.message);
return;
}
} else {
const { bibliographyItems, bibliographyMeta } = await getOneTimeBibliographyOrFallback(
bib.current.itemsCSL, citationStyleXml, styleHasBibliography, format
);

if(format === 'rtf') {
bibStyle = getBibliographyFormatParameters(bibliographyMeta);
separator = '\\\r\n';
preamble = `${bibStyle.tabStops.length ? '\\tx' + bibStyle.tabStops.join(' \\tx') + ' ' : ''}\\li${bibStyle.indent} \\fi${bibStyle.firstLineIndent} \\sl${bibStyle.lineSpacing} \\slmult1 \\sa${bibStyle.entrySpacing} `;
}
fileContents = `{\\rtf ${bibliographyMeta.formatMeta.markupPre}${preamble}${bibliographyItems.map(i => i.value).join(separator)}${bibliographyMeta.formatMeta.markupPost}}`;
// citeprocJS
// fileContents = `${bibliography[0].bibstart}${preamble}${bibliography[1].join(separator)}${bibliography[0].bibend}`;
}

const fileName = `citations.${exportFormats[format].extension}`;
const file = new File(
[fileContents],
fileName,
{ type: exportFormats[format].mime }
);
return file;
}, [handleError, citationStyleXml, styleHasBibliography]);

const updateBibliography = useCallback(() => {
const diff = citeproc.current.batchedUpdates().unwrap();
const itemsLookup = bib.current.itemsRaw.reduce((acc, item) => { acc[item.key] = item; return acc }, {});
Expand Down Expand Up @@ -677,7 +752,6 @@ const BibWebContainer = props => {
setIdentifier('');
setIsTranslating(false);
updateBibliography();
console.log([translationResponse.items[0]], { citationStyleXml, styleHasBibliography });
setItemUnderReview({
item: translationResponse.items[0],
...(await getOneTimeBibliographyOrFallback(
Expand Down Expand Up @@ -720,6 +794,14 @@ const BibWebContainer = props => {
}
}, [addItem, lastDeletedItem, messages, updateBibliography]);

const handleSaveToZoteroShow = useCallback(() => {
setActiveDialog('SAVE_TO_ZOTERO');
}, []);

const handleSaveToZoteroHide = useCallback(() => {
setActiveDialog(null);
}, []);

const fetchCitationStyleXml = useCallback(async () => {
setIsFetchingStyleXml(true);
setCitationStyleXml(await retrieveIndependentStyle(citationStyle));
Expand Down Expand Up @@ -777,9 +859,7 @@ const BibWebContainer = props => {
if(isDataReady && isStyleReady && isCiteprocReady && !isQueryHandled && location.pathname === '/import') {
history.replace('/');
setIsQueryHandled(true);
console.log({ isStyleReady, isDataReady, handleTranslateIdentifier });
(async () => {
console.log({ isStyleReady });
await handleTranslateIdentifier(identifier, null, true);
})();
}
Expand All @@ -805,6 +885,8 @@ const BibWebContainer = props => {


return (<ZBib
getCopyData = { getCopyData }
getFileData = { getFileData }
bibliography = { bibliography }
citationCopyModifiers = { citationCopyModifiers }
citationHtml = { citationHtml }
Expand Down Expand Up @@ -857,13 +939,14 @@ const BibWebContainer = props => {
onTitleChanged = { handleTitleChange }
onHelpClick = { noop }
onReadMore = { handleReadMoreClick }
onSaveToZoteroHide = { noop }
onStyleSwitchCancel = { noop }
onStyleSwitchConfirm = { noop }
onTranslationRequest = { handleTranslateIdentifier }
onCitationStyleChanged={ handleCitationStyleChanged }
onOverride={ handleOverride }
onUndoDelete = { handleUndoDelete }
onSaveToZoteroShow = { handleSaveToZoteroShow }
onSaveToZoteroHide = { handleSaveToZoteroHide }
permalink = { permalink }
stylesData={ stylesData }
styleHasBibliography={ styleHasBibliography }
Expand Down
4 changes: 2 additions & 2 deletions src/js/components/export-tools.jsx
Expand Up @@ -65,8 +65,8 @@ class ExportDialog extends React.Component {
}
}

handleCopy(format) {
const text = this.props.getCopyData(format);
async handleCopy(format) {
const text = await this.props.getCopyData(format);
const result = copy(text);
if(result) {
this.handleClipoardSuccess(format);
Expand Down
3 changes: 2 additions & 1 deletion src/js/components/zbib.jsx
Expand Up @@ -136,7 +136,7 @@ class ZBib extends React.PureComponent {
<p><b>Sentence case:</b> <i>Circadian mood variations in <span style={{color: '#e52e3d', fontWeight: 'bold'}}>T</span>witter content</i></p>
</Confirmation>
<Modal
isOpen={ this.props.isSaveToZoteroVisible }
isOpen={ this.props.activeDialog === 'SAVE_TO_ZOTERO' }
onRequestClose={ () => this.props.onSaveToZoteroHide() }
className={ cx('modal modal-centered') }
>
Expand Down Expand Up @@ -170,6 +170,7 @@ class ZBib extends React.PureComponent {
}

static propTypes = {
activeDialog: PropTypes.string,
citationHtml: PropTypes.string,
citationToCopy: PropTypes.string,
errorMessage: PropTypes.string,
Expand Down
8 changes: 8 additions & 0 deletions src/js/constants/export-formats.js
Expand Up @@ -14,6 +14,14 @@ export default {
isDownloadable: false,
isCopyable: true
},
'plain': { // same as text but for citeprocRS
extension: 'txt',
mime: 'text/plain',
label: 'Copy to Clipboard',
include: 'html',
isDownloadable: false,
isCopyable: true
},
'rtf': {
extension: 'rtf',
mime: 'text/rtf',
Expand Down
42 changes: 6 additions & 36 deletions src/js/utils.jsx
Expand Up @@ -60,7 +60,7 @@ class Fetcher {
// });
// };

const getCiteproc = async (style) => {
const getCiteproc = async (style, format = 'html') => {
const lang = window ? window.navigator.userLanguage || window.navigator.language : null;
const fetcher = new Fetcher();

Expand All @@ -77,7 +77,9 @@ const getCiteproc = async (style) => {
await init();
}

const driverResult = Driver.new({ localeOverride: lang, style, fetcher });
console.log({ localeOverride: lang, format, style, fetcher });

const driverResult = Driver.new({ localeOverride: lang, format, style, fetcher });
const driver = driverResult.unwrap();
await driver.fetchLocales();
return driver;
Expand Down Expand Up @@ -313,37 +315,6 @@ const saveToPermalink = async (url, data) => {
}
};

/**
* copied from https://github.com/zotero/zotero/blob/1f5639da4297ac20fd21223d2004a7cfeef72e21/chrome/content/zotero/xpcom/cite.js#L43
* Convert formatting data from citeproc-js bibliography object into explicit format
* parameters for RTF or word processors
* @param {bib} citeproc-js bibliography object
* @return {Object} Bibliography style parameters.
*/

const getBibliographyFormatParameters = bib => {
var bibStyle = {'tabStops':[], 'indent':0, 'firstLineIndent':0,
'lineSpacing':(240 * bib[0].linespacing),
'entrySpacing':(240 * bib[0].entryspacing)};
if(bib[0].hangingindent) {
bibStyle.indent = 720; // 720 twips = 0.5 in
bibStyle.firstLineIndent = -720; // -720 twips = -0.5 in
} else if(bib[0]['second-field-align']) {
// this is a really sticky issue. the below works for first fields that look like "[1]"
// and "1." otherwise, i have no idea. luckily, this will be good enough 99% of the time.
var alignAt = 24+bib[0].maxoffset*120;
bibStyle.firstLineIndent = -alignAt;
if(bib[0]['second-field-align'] == 'margin') {
bibStyle.tabStops = [0];
} else {
bibStyle.indent = alignAt;
bibStyle.tabStops = [alignAt];
}
}

return bibStyle;
};

const getCitations = (bib, citeproc) => {
const items = bib.itemsRaw.map(item => item.key);
const citations = [];
Expand All @@ -366,10 +337,10 @@ const getCitations = (bib, citeproc) => {
return citations;
};

const getOneTimeBibliographyOrFallback = async (itemsCSL, citationStyleXml, styleHasBibliography) => {
const getOneTimeBibliographyOrFallback = async (itemsCSL, citationStyleXml, styleHasBibliography, format = 'html') => {
var bibliographyItems, bibliographyMeta = null;

const citeproc = await getCiteproc(citationStyleXml);
const citeproc = await getCiteproc(citationStyleXml, format);
citeproc.includeUncited("All").unwrap();
citeproc.insertReferences(itemsCSL).unwrap();

Expand Down Expand Up @@ -713,7 +684,6 @@ export {
dedupMultipleChoiceItems,
fetchFromPermalink,
fetchWithCachedFallback,
getBibliographyFormatParameters,
getOneTimeBibliographyOrFallback,
getCitation,
getCiteproc,
Expand Down

0 comments on commit d0b5ee2

Please sign in to comment.