parse/modules/wikidata/prop.js

  1. /**
  2. * @module input/wikidata
  3. */
  4. import wdk from 'wikidata-sdk'
  5. import fetchFile from '../../../util/fetchFile'
  6. import fetchFileAsync from '../../../util/fetchFileAsync'
  7. import fetchWikidataType from './type'
  8. import parseDate from '../../date'
  9. import parseName from '../../name'
  10. /**
  11. * Get the names of objects from Wikidata IDs
  12. *
  13. * @access private
  14. * @method fetchWikidataLabel
  15. *
  16. * @param {String|Array<String>} q - Wikidata IDs
  17. * @param {String} lang - Language
  18. *
  19. * @return {Array<String>} Array with labels of each prop
  20. */
  21. const fetchWikidataLabel = function (q, lang) {
  22. const ids = Array.isArray(q) ? q : typeof q === 'string' ? q.split('|') : ''
  23. const url = wdk.getEntities(ids, [lang], 'labels')
  24. const entities = JSON.parse(fetchFile(url)).entities || {}
  25. return Object.keys(entities).map(entityKey => (entities[entityKey].labels[lang] || {}).value)
  26. }
  27. /**
  28. * Get series ordinal from qualifiers object
  29. *
  30. * @access private
  31. * @method parseWikidataProp
  32. *
  33. * @param {Object} qualifiers - qualifiers object
  34. *
  35. * @return {Number} series ordinal or -1
  36. */
  37. const parseWikidataP1545 = qualifiers => qualifiers.P1545 ? parseInt(qualifiers.P1545[0]) : -1
  38. /**
  39. * Map holding information on Wikidata fields.
  40. *
  41. * * If false, field should be ignored
  42. * * If string, use as field name
  43. *
  44. * @access private
  45. * @constant propMap
  46. * @default
  47. */
  48. const propMap = {
  49. P31: 'type',
  50. P50: 'author',
  51. P57: 'director',
  52. P86: 'composer',
  53. P98: 'editor',
  54. P110: 'illustrator',
  55. P123: 'publisher',
  56. P136: 'genre',
  57. P212: 'ISBN',
  58. P236: 'ISSN',
  59. P291: 'publisher-place',
  60. P304: 'page',
  61. P348: 'version',
  62. P356: 'DOI',
  63. P393: 'edition',
  64. P433: 'issue',
  65. P478: 'volume',
  66. P577: 'issued',
  67. P655: 'translator',
  68. P698: 'PMID',
  69. P932: 'PMCID',
  70. P953: 'URL',
  71. P957: 'ISBN',
  72. P1104: 'number-of-pages',
  73. P1433: 'container-title',
  74. P1476: 'title',
  75. P2093: 'author',
  76. // ignore
  77. P2860: false, // Cites
  78. P921: false, // Main subject
  79. P3181: false, // OpenCitations bibliographic resource ID
  80. P364: false // Original language of work
  81. }
  82. /**
  83. * Transform property and value from Wikidata format to CSL.
  84. *
  85. * Returns additional _ordinal property on authors.
  86. *
  87. * @access protected
  88. * @method parseWikidataProp
  89. *
  90. * @param {String} name - Property name
  91. * @param {String|Number} [value] - Value
  92. * @param {String} [lang] - Language
  93. *
  94. * @return {Array<String>|String} Array with new prop and value or just the prop when function is called without value
  95. */
  96. const parseWikidataProp = function (name, value, lang) {
  97. if (!propMap.hasOwnProperty(name)) {
  98. logger.info('[set]', `Unknown property: ${name}`)
  99. return undefined
  100. } else if (propMap[name] === false) {
  101. return undefined
  102. }
  103. const cslProp = propMap[name]
  104. if (!value) {
  105. return cslProp
  106. }
  107. const cslValue = ((prop, valueList) => {
  108. const value = valueList[0].value
  109. switch (prop) {
  110. case 'P31':
  111. const type = fetchWikidataType(value)
  112. if (!type) {
  113. logger.warn('[set]', `Wikidata entry type not recognized: ${value}. Defaulting to "book".`)
  114. return 'book'
  115. }
  116. return type
  117. case 'P50':
  118. case 'P57':
  119. case 'P86':
  120. case 'P98':
  121. case 'P110':
  122. case 'P655':
  123. return valueList.map(({value, qualifiers}) => {
  124. const name = parseName(fetchWikidataLabel(value, lang)[0])
  125. name._ordinal = parseWikidataP1545(qualifiers)
  126. return name
  127. })
  128. case 'P577':
  129. return parseDate(value)
  130. case 'P123':
  131. case 'P136':
  132. case 'P291':
  133. case 'P1433':
  134. return fetchWikidataLabel(value, lang)[0]
  135. case 'P2093':
  136. return valueList.map(({value, qualifiers}) => {
  137. const name = parseName(value)
  138. name._ordinal = parseWikidataP1545(qualifiers)
  139. return name
  140. })
  141. default:
  142. return value
  143. }
  144. })(name, value)
  145. return [cslProp, cslValue]
  146. }
  147. /**
  148. * Get the names of objects from Wikidata IDs (async)
  149. *
  150. * @access private
  151. * @method fetchWikidataLabelAsync
  152. *
  153. * @param {String|Array<String>} q - Wikidata IDs
  154. * @param {String} lang - Language
  155. *
  156. * @return {Promise<Array<String>>} Array with labels of each prop
  157. */
  158. const fetchWikidataLabelAsync = async function (q, lang) {
  159. const ids = Array.isArray(q) ? q : typeof q === 'string' ? q.split('|') : ''
  160. const url = wdk.getEntities(ids, [lang], 'labels')
  161. const entities = JSON.parse(await fetchFileAsync(url)).entities || {}
  162. return Object.keys(entities).map(entityKey => (entities[entityKey].labels[lang] || {}).value)
  163. }
  164. /**
  165. * Transform property and value from Wikidata format to CSL (async).
  166. *
  167. * Returns additional _ordinal property on authors.
  168. *
  169. * @access protected
  170. * @method parseWikidataPropAsync
  171. *
  172. * @param {String} prop - Property
  173. * @param {String|Number} value - Value
  174. * @param {String} lang - Language
  175. *
  176. * @return {Promise<Array<String>>} Array with new prop and value
  177. */
  178. const parseWikidataPropAsync = async function (prop, value, lang) {
  179. const cslValue = await (async (prop, valueList) => {
  180. const value = valueList[0].value
  181. switch (prop) {
  182. case 'P50':
  183. case 'P57':
  184. case 'P86':
  185. case 'P98':
  186. case 'P110':
  187. case 'P655':
  188. return Promise.all(valueList.map(async ({value, qualifiers}) => {
  189. const name = parseName((await fetchWikidataLabelAsync(value, lang))[0])
  190. name._ordinal = parseWikidataP1545(qualifiers)
  191. return name
  192. }))
  193. case 'P123':
  194. case 'P136':
  195. case 'P291':
  196. case 'P1433':
  197. return (await fetchWikidataLabelAsync(value, lang))[0]
  198. }
  199. })(prop, value)
  200. if (cslValue) {
  201. return [parseWikidataProp(prop), cslValue]
  202. } else {
  203. return parseWikidataProp(prop, value, lang)
  204. }
  205. }
  206. export {
  207. parseWikidataProp as parse,
  208. parseWikidataPropAsync as parseAsync,
  209. parseWikidataProp as default
  210. }