├── .gitignore ├── README.md ├── schema.json └── scripts ├── update-gz └── update-schema /.gitignore: -------------------------------------------------------------------------------- 1 | schema.json.gz 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zotero Data Schema 2 | 3 | This repo contains a schema file providing most of the information necessary for working with the Zotero data model. The file is served from the Zotero API at https://api.zotero.org/schema. 4 | 5 | The schema file contains a JSON object with the following top-level properties: 6 | 7 | - `version`: The version number of the schema 8 | - `itemTypes`: A list of item types and their associated fields and creator types. 9 | - `meta`: Additional field info, such as which fields are date fields 10 | - `csl`: CSL type/field/creator mappings for converting between Zotero data and CSL JSON 11 | - `locales`: Localized strings for item types, fields, and creator types in all locales supported by Zotero 12 | 13 | ## Downloading the schema from your app 14 | 15 | The schema file is large, so be sure your app’s HTTP client is passing `Accept-Encoding: gzip` with the download request. Cache the file along with its `ETag` header and, when checking for updates, make a conditional request using `If-None-Match: `. In most cases, you will receive a `304` and should continue using the cached version. 16 | -------------------------------------------------------------------------------- /scripts/update-gz: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | cd $dir/.. 5 | 6 | file=schema.json.gz 7 | tmp_file=$file-tmp 8 | 9 | gzip -c schema.json > $tmp_file 10 | 11 | DIFF=$(diff $file $tmp_file) 12 | if [ "$DIFF" != "" ] 13 | then 14 | echo "$file updated" 15 | mv $tmp_file $file 16 | else 17 | echo "Schema hasn't changed" 18 | rm $tmp_file 19 | fi 20 | -------------------------------------------------------------------------------- /scripts/update-schema: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Update schema.json with locale strings from zotero.properties for the types and fields defined 4 | // in schema.json 5 | // 6 | // This is generally run automatically by locale/run from the zotero-build repo. 7 | // 8 | // To add a new type or field, add it to schema.json, add the English string to zotero.properties, 9 | // and run locale/run, which merges English strings, pulls translations from Transifex, and runs 10 | // this script. 11 | 12 | const path = require('path'); 13 | const fs = require('fs'); 14 | 15 | async function run() { 16 | var clientDir = path.join(__dirname, '..', '..', '..', '..'); 17 | if (!fs.existsSync(path.join(clientDir, 'chrome'))) { 18 | console.error(`Not within a Zotero client directory`); 19 | process.exitCode = 1; 20 | return; 21 | } 22 | 23 | var oldJSON = fs.readFileSync('schema.json').toString(); 24 | var data = JSON.parse(oldJSON); 25 | var version = parseInt(data.version); 26 | 27 | // Sort item types 28 | data.itemTypes.sort((a, b) => { 29 | return a.itemType.localeCompare(b.itemType); 30 | }); 31 | 32 | // Regenerate localized strings using the files in chrome/locale 33 | data.locales = {}; 34 | var itemTypes = data.itemTypes.map(x => x.itemType); 35 | var fields = Array.from( 36 | new Set( 37 | data.itemTypes.map(x => x.fields).reduce( 38 | (accumulator, currentValue) => { 39 | return accumulator.concat(currentValue.map(x => x.field)); 40 | }, 41 | [ 42 | 'itemType', 43 | // Base or primary fields used as search conditions 44 | 'authority', 45 | 'medium', 46 | 'number', 47 | 'dateAdded', 48 | 'dateModified' 49 | ] 50 | ) 51 | ) 52 | ); 53 | var creatorTypes = Array.from(new Set(data.itemTypes.map(x => x.creatorTypes).reduce((accumulator, currentValue) => { 54 | return accumulator.concat(currentValue.map(x => x.creatorType)); 55 | }, []))); 56 | var cmp = (a, b) => { 57 | return a.localeCompare(b, 'en', { sensitivity: 'base' }); 58 | }; 59 | itemTypes.sort(cmp); 60 | fields.sort(cmp); 61 | creatorTypes.sort(cmp); 62 | 63 | var dirs = fs.readdirSync(path.join(clientDir, 'chrome', 'locale')); 64 | for (let locale of dirs) { 65 | if (!/^[a-z]{2}(-[A-Z]{2})?/.test(locale)) { 66 | continue; 67 | } 68 | 69 | data.locales[locale] = { 70 | itemTypes: {}, 71 | fields: {}, 72 | creatorTypes: {} 73 | }; 74 | 75 | // Read in zotero.properties file for locale 76 | let props = fs.readFileSync( 77 | path.join(clientDir, 'chrome', 'locale', locale, 'zotero', 'zotero.properties') 78 | ); 79 | let lines = props.toString().split(/\n/g); 80 | let strings = new Map(); 81 | for (let line of lines) { 82 | let [ key, str ] = line.split(/\s*=\s*/); 83 | if (!key) continue; 84 | strings.set(key, str); 85 | } 86 | 87 | data.locales[locale].itemTypes = getLocalizedStrings(locale, strings, 'itemTypes', itemTypes); 88 | data.locales[locale].fields = getLocalizedStrings(locale, strings, 'itemFields', fields); 89 | data.locales[locale].creatorTypes = getLocalizedStrings(locale, strings, 'creatorTypes', creatorTypes); 90 | } 91 | 92 | // Write new file with incremented version 93 | var newJSON = JSON.stringify(data, null, '\t') + "\n"; 94 | if (oldJSON != newJSON) { 95 | console.log("Schema updated"); 96 | data.version = version + 1; 97 | newJSON = JSON.stringify(data, null, '\t') + "\n" 98 | fs.writeFileSync('schema.json', newJSON); 99 | } 100 | else { 101 | console.log("Schema hasn't changed"); 102 | } 103 | } 104 | 105 | function getLocalizedStrings(locale, strings, prefix, subjects) { 106 | var data = {}; 107 | for (let subject of subjects) { 108 | let key = prefix + '.' + subject; 109 | if (!strings.has(key)) { 110 | // 'type' is only a base field, so it doesn't have a string 111 | if (key == 'itemFields.type') { 112 | continue; 113 | } 114 | throw new Error(`Localized string ${key} missing for ${locale}. ` 115 | + "Did you update the en-US zotero.properties with new types and fields " 116 | + "and merge the new English strings?\n"); 117 | } 118 | data[subject] = strings.get(key); 119 | } 120 | return data; 121 | } 122 | 123 | (async function () { 124 | try { 125 | await run(); 126 | } 127 | catch (e) { 128 | console.error(e); 129 | } 130 | })(); 131 | --------------------------------------------------------------------------------