citeproc-rb 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/README +93 -0
  2. data/Rakefile +73 -0
  3. data/TODO +25 -0
  4. data/VERSION +1 -0
  5. data/data/locales/locales_en.xml +89 -0
  6. data/data/locales/locales_nl.xml +96 -0
  7. data/data/styles/ama.csl +184 -0
  8. data/data/styles/apa.csl +230 -0
  9. data/data/styles/apsa.csl +175 -0
  10. data/data/styles/asa.csl +215 -0
  11. data/data/styles/chicago-author-date.csl +361 -0
  12. data/data/styles/chicago-fullnote-bibliography.csl +665 -0
  13. data/data/styles/chicago-note-bibliography.csl +437 -0
  14. data/data/styles/chicago-note.csl +383 -0
  15. data/data/styles/harvard.csl +185 -0
  16. data/data/styles/ieee.csl +111 -0
  17. data/data/styles/mhra.csl +317 -0
  18. data/data/styles/mla.csl +173 -0
  19. data/data/styles/nature.csl +87 -0
  20. data/data/styles/nlm.csl +118 -0
  21. data/examples/citeproc_driver.rb +174 -0
  22. data/lib/citeproc.rb +28 -0
  23. data/lib/citeproc/csl.rb +647 -0
  24. data/lib/citeproc/csl_parser.rb +479 -0
  25. data/lib/citeproc/csl_processor.rb +485 -0
  26. data/lib/citeproc/csl_reference.rb +228 -0
  27. data/lib/citeproc/filters/bibo/bibo.rb +635 -0
  28. data/lib/citeproc/filters/bibo/bibo_input_filter.rb +242 -0
  29. data/lib/citeproc/filters/bibo/bibo_utils.rb +75 -0
  30. data/lib/citeproc/filters/csl_citation/csl_citation.rb +137 -0
  31. data/lib/citeproc/filters/csl_citation/csl_citation_input_filter.rb +274 -0
  32. data/lib/citeproc/formatters/base_formatter.rb +115 -0
  33. data/lib/citeproc/formatters/xhtml_formatter.rb +144 -0
  34. data/lib/citeproc/input_filter.rb +97 -0
  35. data/test/fixtures/bibo_test_data.n3 +191 -0
  36. data/test/fixtures/bibo_test_data.yaml +96 -0
  37. data/test/fixtures/bibo_test_how_to_cook.n3 +63 -0
  38. data/test/fixtures/bibo_test_zotero.rdf +129 -0
  39. data/test/fixtures/csl_test_data.json +62 -0
  40. data/test/fixtures/csl_test_data.yaml +8 -0
  41. data/test/fixtures/locales/test_locale_en.xml +89 -0
  42. data/test/fixtures/styles/test_csl_parse.csl +230 -0
  43. data/test/fixtures/styles/test_groups.csl +32 -0
  44. data/test/fixtures/styles/test_names.csl +46 -0
  45. data/test/fixtures/styles/test_sort.csl +125 -0
  46. data/test/fixtures/styles/test_text_terms.csl +40 -0
  47. data/test/test_all.rb +14 -0
  48. data/test/test_bibo.rb +20 -0
  49. data/test/test_csl.rb +136 -0
  50. data/test/test_csl_parser.rb +222 -0
  51. data/test/test_sample_bibo.rb +82 -0
  52. metadata +101 -0
@@ -0,0 +1,485 @@
1
+ # == Synopsis
2
+ #
3
+ # Processes a series of document references according to a given CSL style.
4
+ # Builds a directed graph of ProcessedNodes, which can be used by a Formatter.
5
+ #
6
+ # == Author
7
+ #
8
+ # Liam Magee
9
+ #
10
+ # == Copyright
11
+ #
12
+ # Copyright (c) 2008, Liam Magee.
13
+ # Licensed under the same terms as Ruby - see http://www.ruby-lang.org/en/LICENSE.txt.
14
+ #
15
+
16
+ module Citeproc
17
+
18
+ # Represents a processed part of the CSL tree with results
19
+ class ProcessedNode
20
+ attr_reader :element, :results, :children
21
+
22
+ def initialize(element, results, children)
23
+ @element = element
24
+ @results = results
25
+ @children = children
26
+ end
27
+
28
+ def empty?
29
+ results.empty?
30
+ end
31
+ end
32
+
33
+ class CslProcessor
34
+
35
+ def init_process(filter, style, locale)
36
+ @substitutions = []
37
+ @filter = filter
38
+ @style = style
39
+ @locale = locale
40
+
41
+ # Citation number & label
42
+ @citation_number = 1
43
+ @citation_label = "#"
44
+ end
45
+
46
+
47
+ def process_citation(filter, style, locale)
48
+ process_top_node(style.citation, filter, style, locale)
49
+ end
50
+
51
+
52
+ def process_bibliography(filter, style, locale)
53
+ process_top_node(style.bibliography, filter, style, locale)
54
+ end
55
+
56
+
57
+ def process_top_node(node, filter, style, locale)
58
+ init_process(filter, style, locale)
59
+ nodes = []
60
+ if node
61
+ @filter.each_sorted_citation(generate_sort_keys(node)) do |citation|
62
+ nodes << process_layout(node.layout) if node.layout
63
+ @citation_number += 1
64
+ end
65
+ end
66
+ nodes
67
+ end
68
+
69
+ def process_all(filter, style, locale)
70
+ results = []
71
+ results << process_citation(filter, style, locale)
72
+ results << process_bibliography(filter, style, locale)
73
+ results
74
+ end
75
+
76
+
77
+ def generate_sort_keys(node)
78
+ key_sets = []
79
+ if node.sort
80
+ @filter.each_citation do |citation|
81
+ keys = []
82
+ keys << citation
83
+ node.sort.keys.each do |key|
84
+ tmp = find_keys(process_element(key))
85
+ keys << tmp.flatten if tmp
86
+ end
87
+ key_sets << keys
88
+ end
89
+ end
90
+ key_sets
91
+ end
92
+
93
+
94
+ def find_keys(node)
95
+ results = []
96
+ node.children.compact.each do |n|
97
+ results << n.results
98
+ results << find_keys(n)
99
+ end
100
+ results.flatten
101
+ end
102
+
103
+
104
+
105
+ def process_layout(layout)
106
+ elements = process_elements(layout.elements)
107
+ process(layout, [], elements)
108
+ end
109
+
110
+
111
+ def process_elements(elements, context = nil)
112
+ results = []
113
+ elements.each do |element|
114
+ results << process_element(element, context) unless substituted?(element)
115
+ end
116
+ results
117
+ end
118
+
119
+ # Less than ideal... Checks if an element has already been substituted.
120
+ # In the case of variables, this is complicated due to the possibility
121
+ # of multiple variable names in the one attribute.
122
+ def substituted?(element)
123
+ return true if @substitutions.include?(element)
124
+ @substitutions.each do |sub|
125
+ if sub.kind_of?(ShortNames) and element.kind_of?(ShortNames)
126
+ element.variable.split.each do |e|
127
+ return true if e == sub.variable
128
+ end
129
+ end
130
+ end
131
+ false
132
+ end
133
+
134
+
135
+ def process_element(element, context = nil)
136
+ case
137
+ when element.kind_of?(Names)
138
+ process_names(element)
139
+ # This is a special case for names declared within the substitute node of another names context.
140
+ when (element.kind_of?(ShortNames) and context)
141
+ process_name_series(element.variable, context)
142
+ when element.kind_of?(Date)
143
+ process_date(element)
144
+ when element.kind_of?(Text)
145
+ process_text(element)
146
+ when element.kind_of?(Label)
147
+ process_label(element, context)
148
+ when element.kind_of?(Group)
149
+ process_group(element)
150
+ when element.kind_of?(ConditionGroup)
151
+ process_group_conditions(element)
152
+ end
153
+ end
154
+
155
+
156
+ def process_names(names)
157
+ result = process_name_series(names.variable, names)
158
+
159
+ # Process the rest of the substitute node, if the result is blank
160
+ if !result or result.empty?
161
+ names.substitute.elements.each do |sub|
162
+ tmp = process_element(sub, names)
163
+ if tmp and !tmp.empty?
164
+ # Put this node on the substitutions stack
165
+ @substitutions << sub
166
+ result = tmp
167
+ break
168
+ end
169
+ end if names.substitute
170
+ end
171
+ result
172
+ end
173
+
174
+
175
+ def process_name_series(variable, name_context)
176
+ name_def = name_context.name
177
+
178
+ # Format the names
179
+ name_groups = []
180
+
181
+ process_name(name_context, variable) do |name_strs, role|
182
+ labels = []
183
+ name_context.labels.each do |label|
184
+ labels << process_label(label, role)
185
+ end
186
+ tmp = process(name_def, name_strs, labels)
187
+ name_groups << tmp
188
+ end
189
+ result = process(name_context, [], name_groups)
190
+ result
191
+ end
192
+
193
+ def process_name(name_context, roles)
194
+ name_str = ''
195
+ name_def = name_context.name
196
+ roles.split.each do |r|
197
+ names = []
198
+ et_al_counter = 0
199
+ @filter.extract_contributor(r, name_def.name_as_sort_order) do |n|
200
+ given_names = n.given_name
201
+ if name_def.form == "short"
202
+ name_str = "#{n.family_name}"
203
+ elsif name_def.name_as_sort_order
204
+ sep = name_def.sort_separator ? name_def.sort_separator : " "
205
+ name_str = n.family_name + sep + process_given_name(name_def, given_names)
206
+ else
207
+ name_str = process_given_name(name_def, given_names) + n.family_name
208
+ end
209
+ names << name_str.strip
210
+ end
211
+ # TODO: place et-al, collapse and disambiguate logic here
212
+ yield names, r if !names.empty?
213
+ end
214
+ end
215
+
216
+ # Need to add sort
217
+ def process_given_name(name_def, given_names)
218
+ if given_names
219
+ if name_def.initialize_with
220
+ given_names.split.collect{|n| n[0..0].upcase + name_def.initialize_with }.join
221
+ else
222
+ given_names
223
+ end
224
+ end
225
+ end
226
+
227
+
228
+ def process_label(label, variable = nil)
229
+ results = []
230
+ if label.kind_of?(Label) and label.variable
231
+ var, singular = @filter.extract_label(label)
232
+ results << @locale.term_text(var, label.form, singular) if var and @locale
233
+ elsif variable
234
+ tmp = @locale.term_text(variable, label.form, singular) if @locale
235
+ results << tmp if tmp
236
+ end
237
+ results << '.' if label.include_period and !results.empty?
238
+ process(label, results, [])
239
+ end
240
+
241
+
242
+ def process_date(date)
243
+ d = @filter.extract_date(date.variable)
244
+ elements = []
245
+ date.dateparts.each do |datepart|
246
+ elements << process_datepart(datepart, d)
247
+ end
248
+ #puts elements
249
+ process(date, [], elements)
250
+ end
251
+
252
+
253
+ def process_datepart(datepart, date)
254
+ process(datepart, [get_datepart(datepart, date)], []) if date
255
+ end
256
+
257
+
258
+ def get_datepart(datepart, date)
259
+ #puts date.class
260
+ #puts date
261
+ if date.is_a?(String)
262
+ # Can't do much here for now...
263
+ date
264
+ else
265
+ case datepart.name
266
+ when "month"
267
+ case datepart.form
268
+ when "short"
269
+ date.strftime("%b") + (datepart.include_period ? '.' : '')
270
+ when "numeric"
271
+ date.month.to_s
272
+ when "numeric-leading-zeros"
273
+ date.strftime("%d")
274
+ else
275
+ date.strftime("%B")
276
+ end
277
+ when "day"
278
+ if datepart.form == "numeric"
279
+ date.day.to_s
280
+ elsif datepart.form == "numeric-leading-zeros" # Leading zeros
281
+ date.strftime("%d")
282
+ else # Ordinal - NB: not internationalised
283
+ if date.day % 10 == 1 and date.day / 10 != 1
284
+ date.day.to_s + "st"
285
+ elsif date.day % 10 == 2 and date.day / 10 != 1
286
+ date.day.to_s + "nd"
287
+ elsif date.day % 10 == 3 and date.day / 10 != 1
288
+ date.day.to_s + "rd"
289
+ else
290
+ date.day.to_s + "th"
291
+ end
292
+ end
293
+ when "year"
294
+ if datepart.form == "short"
295
+ date.strftime("%y")
296
+ else
297
+ date.strftime("%Y")
298
+ end
299
+ else
300
+ date.to_s
301
+ end
302
+ end
303
+ end
304
+
305
+
306
+ def process_text(text)
307
+ node = nil
308
+ case
309
+ when text.kind_of?(VariableText)
310
+ tmp = text.variable.split.collect { |e| process_variable(e) }
311
+ node = process(text, tmp, [])
312
+ when text.kind_of?(MacroText)
313
+ node = expand_macro(text)
314
+ when text.kind_of?(TermText)
315
+ # Fix
316
+ node = process(text, [text.value], [])
317
+ when text.kind_of?(ValueText)
318
+ node = process(text, [text.value], [])
319
+ end
320
+ node
321
+ end
322
+
323
+
324
+ def process_variable(variable)
325
+ case variable
326
+ when "citation-number"
327
+ @citation_number
328
+ when "citation-label"
329
+ @citation_label
330
+ else
331
+ @filter.extract_variable(variable)
332
+ end
333
+ end
334
+
335
+
336
+
337
+
338
+ def process_group(group)
339
+ process(group, [], process_elements(group.elements)) if eval_group_variable(group)
340
+ end
341
+
342
+
343
+ # Test whether there is a non-blank variable in this group
344
+ # TODO: Very poor perfomance (forces 2-time evaluation of all rules) and
345
+ # incorrect treatment of conditions - evaluates ALL for variable resolution.
346
+ def eval_group_variable(group)
347
+ group.elements.flatten.each do |element|
348
+ if element.kind_of? VariableText
349
+ var = element.variable
350
+ return true if eval_variable(var)
351
+ elsif element.kind_of? Date
352
+ var = element.variable
353
+ return true if eval_variable(var)
354
+ elsif element.kind_of? Names
355
+ var = element.variable
356
+ return true if eval_variable(var)
357
+ element.substitute.names.each do |name|
358
+ var = name.variable
359
+ return true if eval_variable(var)
360
+ end if element.substitute
361
+ elsif element.kind_of? MacroText
362
+ macro = get_macro(element)
363
+ if macro
364
+ return true if eval_group_variable(macro)
365
+ else
366
+ raise "Could not find macro with the name of: #{element.value}."
367
+ end
368
+ elsif element.kind_of? Group
369
+ return true if eval_group_variable(element)
370
+ elsif element.kind_of? ConditionGroup
371
+ element.conditions.each do |c|
372
+ return true if eval_group_variable(c)
373
+ end
374
+ end
375
+ end if group.elements
376
+ false
377
+ end
378
+
379
+ def eval_variable(var)
380
+ var.split.each do |v|
381
+ tmp = @filter.extract_variable(v)
382
+ if tmp and !tmp.to_s.empty?
383
+ return true
384
+ end
385
+ end
386
+ false
387
+ end
388
+
389
+
390
+ def process_group_conditions(conditions)
391
+ node = nil
392
+ conditions.each do |condition|
393
+ if met?(condition)
394
+ node = process(condition, [], process_elements(condition.elements))
395
+ break
396
+ end
397
+ end
398
+ node
399
+ end
400
+
401
+ def met?(condition)
402
+ if condition.type
403
+ met_type?(condition)
404
+ elsif condition.variable
405
+ met_variable?(condition)
406
+ elsif condition.position
407
+ met_position?(condition)
408
+ elsif condition.disambiguate
409
+ met_disambiguate?(condition)
410
+ elsif condition.locator
411
+ met_locator?(condition)
412
+ else
413
+ true
414
+ end
415
+ end
416
+
417
+ def met_type?(condition)
418
+ conditions = condition.type.split
419
+ if condition.match == "none"
420
+ !conditions.include?(@filter.resolve_type)
421
+ else
422
+ conditions.include?(@filter.resolve_type)
423
+ end
424
+ end
425
+
426
+ def met_variable?(condition)
427
+ conditions = condition.variable.split
428
+ if condition.match == "none"
429
+ conditions.each do |c|
430
+ return false if @filter.extract_variable(c)
431
+ end
432
+ return true
433
+ else
434
+ conditions.each do |c|
435
+ tmp = @filter.extract_variable(c)
436
+ return false if !tmp
437
+ case tmp.class
438
+ when Array
439
+ return !tmp.empty?
440
+ when String
441
+ return !tmp.strip.empty?
442
+ else
443
+ return false
444
+ end
445
+ #return true if (tmp != nil and (tmp.is_a?(Array) ? !tmp.empty? : true))
446
+ end
447
+ return false
448
+ end
449
+ end
450
+
451
+
452
+ def met_position?(condition)
453
+ false # TODO
454
+ end
455
+
456
+ def met_disambiguate?(condition)
457
+ false # TODO
458
+ end
459
+
460
+ def met_locator?(condition)
461
+ false # TODO
462
+ end
463
+
464
+
465
+ def get_macro(macro)
466
+ @style.macros.each do |m|
467
+ return m if m.name == macro.macro
468
+ end
469
+ nil
470
+ end
471
+
472
+ def expand_macro(macro)
473
+ results = []
474
+ m = get_macro(macro)
475
+ results = process_elements(m.elements) if m
476
+ process(macro, [], results)
477
+ end
478
+
479
+
480
+ def process(element, results, children)
481
+ ProcessedNode.new(element, results, children)
482
+ end
483
+
484
+ end
485
+ end