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,479 @@
1
+ #! /usr/bin/env ruby
2
+ # == Synopsis
3
+ #
4
+ # The CSL parser builds a CSL model from a file containing XML conforming to the
5
+ # CSL RelaxNG schema.
6
+ #
7
+ # Code usage:
8
+ # parser = CSLParser.new(io or file)
9
+ # style = parser.style
10
+ #
11
+ # Command line usage:
12
+ # ./lib/csl_parser csl_file
13
+ #
14
+ # == Author
15
+ #
16
+ # Liam Magee
17
+ #
18
+ # == Copyright
19
+ #
20
+ # Copyright (c) 2007, Liam Magee.
21
+ # Licensed under the same terms as Ruby - see http://www.ruby-lang.org/en/LICENSE.txt.
22
+ #
23
+ require 'rubygems'
24
+ require 'open-uri'
25
+ require 'rexml/document'
26
+ require 'builder'
27
+
28
+
29
+ module Citeproc
30
+ class CslParser
31
+ attr_reader :style, :terms
32
+
33
+ def initialize(source)
34
+ build(source)
35
+ end
36
+
37
+
38
+ # ---------- Private methods for parsing the CSL DOM -----------
39
+
40
+ # Mixin for formatting types
41
+
42
+
43
+ private
44
+ def build(source)
45
+ if source.kind_of?(String)
46
+ @document = REXML::Document.new(open(source))
47
+ elsif source.kind_of?(IO)
48
+ @document = REXML::Document.new(source)
49
+ end
50
+ if @document.elements["style"]
51
+ build_style
52
+ elsif @document.elements["terms"]
53
+ build_terms
54
+ end
55
+ end
56
+
57
+ def build_style
58
+ style_node = @document.elements["style"]
59
+ @style = Style.new(
60
+ style_class = attr(style_node, :class),
61
+ lang = attr(style_node, :lang, 'xml'),
62
+ info = build_info(style_node),
63
+ terms = build_terms(style_node),
64
+ macros = build_macros(style_node),
65
+ citation = build_citation(style_node),
66
+ bibliography = build_bibliography(style_node)
67
+ )
68
+ end
69
+
70
+ def build_info(style_node)
71
+ info_node = style_node.elements["info"]
72
+ info = Info.new(
73
+ id = text(info_node, :id),
74
+ title = info_text(info_node, :title),
75
+ updated = time(info_node, :updated),
76
+ authors = build_authors(info_node),
77
+ categories = build_categories(info_node),
78
+ contributors = build_contributors(info_node),
79
+ published = time(info_node, :published),
80
+ links = build_links(info_node),
81
+ rights = info_text(info_node, :rights),
82
+ source = text(info_node, :source),
83
+ summary = info_text(info_node, :summary)
84
+ )
85
+ end
86
+
87
+ def build_authors(node)
88
+ authors = []
89
+ node.elements.each("author") do |author|
90
+ authors << Author.new(
91
+ text(author, :name),
92
+ text(author, :email),
93
+ text(author, :uri)
94
+ )
95
+ end
96
+ authors
97
+ end
98
+
99
+ def build_categories(node)
100
+ categories = []
101
+ node.elements.each("category") do |category|
102
+ categories << Category.new(
103
+ attr(category, :term),
104
+ attr(category, :scheme),
105
+ attr(category, :label)
106
+ )
107
+ end
108
+ categories
109
+ end
110
+
111
+ def build_contributors(node)
112
+ contributors = []
113
+ node.elements.each("contributor") do |contributor|
114
+ contributors << Contributor.new(
115
+ text(contributor, :name),
116
+ text(contributor, :email),
117
+ text(contributor, :uri)
118
+ )
119
+ end
120
+ contributors
121
+ end
122
+
123
+ def build_links(node)
124
+ links = []
125
+ node.elements.each("link") do |link|
126
+ links << attr(link, :href)
127
+ end
128
+ links
129
+ end
130
+
131
+ def build_terms(node = nil)
132
+ terms_node = @document.elements["terms"] if !node
133
+ if terms_node
134
+ @terms = Terms.new(
135
+ locales = build_locales(terms_node)
136
+ )
137
+ end
138
+ @terms
139
+ end
140
+
141
+ def build_locales(node)
142
+ locales = []
143
+ node.elements.each("locale") do |locale|
144
+ locales << Locale.new(
145
+ language = attr(locale, "xml:lang"),
146
+ terms = build_individual_terms(locale)
147
+ )
148
+ end
149
+ locales
150
+ end
151
+
152
+ def build_individual_terms(node)
153
+ terms = []
154
+ node.elements.each("term") do |term|
155
+ if term.elements["single"] or term.elements["multiple"]
156
+ t = CompoundTerm.new(
157
+ form = attr(term, "form"),
158
+ name = attr(term, "name"),
159
+ include_period = attr(term, "include_period")
160
+ )
161
+ # TODO: Can there be multiple single and multiple nodes in practice, as per
162
+ # the CSL spec?
163
+ t.single = text(term.elements["single"])
164
+ t.multiple = text(term.elements["multiple"])
165
+ terms << t
166
+ else
167
+ terms << SimpleTerm.new(
168
+ form = attr(term, "form"),
169
+ name = attr(term, "name"),
170
+ include_period = attr(term, "include_period"),
171
+ text = text(term)
172
+ )
173
+ end
174
+ end
175
+ terms
176
+ end
177
+
178
+
179
+ def build_macros(node)
180
+ macros = []
181
+ node.elements.each("macro") do |m|
182
+ macro = Macro.new(
183
+ name = attr(m, :name)
184
+ )
185
+ build_elements(m, macro)
186
+ macros << macro
187
+ end
188
+ macros
189
+ end
190
+
191
+
192
+ def build_citation(node)
193
+ citation_node = node.elements["citation"]
194
+ citation = CitationContext.new(
195
+ options = build_options(citation_node),
196
+ sort = build_sort(citation_node),
197
+ layout = build_layout(citation_node)
198
+ )
199
+ citation
200
+ end
201
+
202
+ def build_bibliography(node)
203
+ bibliography_node = node.elements["bibliography"]
204
+ if bibliography_node
205
+ bibliography = BibliographyContext.new(
206
+ options = build_options(bibliography_node),
207
+ sort = build_sort(bibliography_node),
208
+ layout = build_layout(bibliography_node)
209
+ )
210
+ end
211
+ bibliography
212
+ end
213
+
214
+
215
+ def build_citation(node)
216
+ citation_node = node.elements["citation"]
217
+ if citation_node
218
+ citation = CitationContext.new(
219
+ options = build_options(citation_node),
220
+ sort = build_sort(citation_node),
221
+ layout = build_layout(citation_node)
222
+ )
223
+ end
224
+ citation
225
+ end
226
+
227
+
228
+ # Simplification of option model
229
+ def build_options(node)
230
+ options = {}
231
+ node.elements.each("option") do |option|
232
+ options[attr(option, :name)] = attr(option, :value)
233
+ end
234
+ options
235
+ end
236
+
237
+ def build_sort(node)
238
+ keys = []
239
+ sort = Sort.new
240
+ node.elements.each("sort/key") do |key_node|
241
+ key = build_text(key_node)
242
+ sort << key
243
+
244
+ # value = key.attribute('variable') ? attr(key, :variable) :
245
+ # (key.attribute('macro') ? attr(key, :macro) : attr(key, :sort))
246
+ # type = key.attribute('variable') ? 'variable' : 'macro'
247
+ # keys << SortKey.new(type, value)
248
+ end
249
+ sort
250
+ end
251
+
252
+ def build_layout(node)
253
+ layout_node = node.elements["layout"]
254
+ if layout_node
255
+ layout = Layout.new(
256
+ formatting = build_formatting(layout_node),
257
+ delimiters = build_delimiters(layout_node)
258
+ )
259
+ build_elements(layout_node, layout)
260
+ end
261
+ layout
262
+ end
263
+
264
+
265
+ # 'obj' should be some instance of GroupingElement
266
+ def build_elements(node, obj)
267
+ node.elements.each do |e|
268
+ case
269
+ when e.name == "names"
270
+ obj << (build_names(e))
271
+ when e.name == "date"
272
+ obj << (build_date(e))
273
+ when e.name == "label"
274
+ obj << (build_label(e))
275
+ when e.name == "text"
276
+ obj << (build_text(e))
277
+ when e.name == "group"
278
+ obj << (build_group(e))
279
+ when e.name == "choose"
280
+ obj << (build_conditions(e))
281
+ end
282
+ end
283
+ end
284
+
285
+ def build_names(node)
286
+ name = node.elements["name"]
287
+ # if this a full Names node?
288
+ if name
289
+ names = Names.new(
290
+ formatting = build_formatting(node),
291
+ delimiters = build_delimiters(node),
292
+ variable = attr(node, :variable)
293
+ )
294
+ if node.elements["name"]
295
+ name = node.elements["name"]
296
+ names.name = Name.new(
297
+ formatting = build_formatting(name),
298
+ delimiters = build_delimiters(name),
299
+ form = attr(name, :form),
300
+ _and = attr(name, :and),
301
+ delimiter_precedes_last = attr(name, 'delimiter-precedes-last'),
302
+ name_as_sort_order = attr(name, 'name-as-sort-order'),
303
+ sort_separator = attr(name, 'sort-separator'),
304
+ initialize_with = attr(name, 'initialize-with')
305
+ )
306
+ end
307
+ node.elements.each("label") do |label|
308
+ names.add_label( NameLabel.new(
309
+ formatting = build_formatting(label),
310
+ include_period = attr(label, :include_period),
311
+ form = attr(label, :form)
312
+ ) )
313
+ end
314
+ if node.elements["substitute"]
315
+ names.substitute = build_substitute(node.elements["substitute"])
316
+ end
317
+ else
318
+ names = ShortNames.new(
319
+ formatting = build_formatting(node),
320
+ delimiters = build_delimiters(node),
321
+ variable = attr(node, :variable)
322
+ )
323
+ end
324
+ names
325
+ end
326
+
327
+ def build_substitute(node)
328
+ substitute = Substitute.new
329
+ build_elements(node, substitute)
330
+ substitute
331
+ end
332
+
333
+ def build_date(node)
334
+ date = Date.new(
335
+ formatting = build_formatting(node),
336
+ delimiters = build_delimiters(node),
337
+ variable = attr(node, :variable)
338
+ )
339
+ node.elements.each("date-part") do |dp|
340
+ date << DatePart.new(
341
+ formatting = build_formatting(dp),
342
+ name = attr(dp, :name),
343
+ form = attr(dp, :form)
344
+ )
345
+ end
346
+ date
347
+ end
348
+
349
+
350
+ def build_label(node)
351
+ label = Label.new(
352
+ formatting = build_formatting(node),
353
+ include_period = attr(node, 'include-period'),
354
+ form = attr(node, :form),
355
+ variable = attr(node, :variable)
356
+ )
357
+ label
358
+ end
359
+
360
+
361
+ def build_text(node)
362
+ case
363
+ when attr(node, :variable)
364
+ text = VariableText.new(
365
+ formatting = build_formatting(node),
366
+ delimiters = build_delimiters(node),
367
+ variable = attr(node, :variable)
368
+ )
369
+ when attr(node, :macro)
370
+ text = MacroText.new(
371
+ formatting = build_formatting(node),
372
+ macro = attr(node, :macro)
373
+ )
374
+ when attr(node, :term)
375
+ text = TermText.new(
376
+ formatting = build_formatting(node),
377
+ term = attr(node, :term)
378
+ )
379
+ when attr(node, :value)
380
+ text = ValueText.new(
381
+ formatting = build_formatting(node),
382
+ value = attr(node, :value)
383
+ )
384
+ end
385
+ text
386
+ end
387
+
388
+ def build_group(node)
389
+ group = Group.new(
390
+ formatting = build_formatting(node),
391
+ delimiters = build_delimiters(node),
392
+ class_name = attr(node, :class)
393
+ )
394
+ build_elements(node, group)
395
+ group
396
+ end
397
+
398
+ def build_conditions(choose_node)
399
+ conditions = ConditionGroup.new
400
+ conditions << build_condition(choose_node.elements["if"])
401
+ choose_node.elements.each("else-if") do |c|
402
+ conditions << build_condition(c)
403
+ end
404
+ end_node = choose_node.elements["else"]
405
+ conditions << build_condition(end_node, false) if end_node
406
+ conditions
407
+ end
408
+
409
+ def build_condition(node, include_attributes = true)
410
+ condition = Condition.new
411
+ attrs = [:type, :variable, :position, :disambiguate, :locator, :match]
412
+ attrs.each do |a|
413
+ condition.send("#{a.to_s}=", attr(node, a))
414
+ end if include_attributes
415
+ build_elements(node, condition)
416
+ condition
417
+ end
418
+
419
+
420
+ def build_formatting(node)
421
+ formatting_options = {}
422
+ Formatting::FORMATTING_TERMS.each do |ft|
423
+ formatting_options[ft] = attr(node, ft)
424
+ end
425
+ formatting_options
426
+ end
427
+
428
+ def build_delimiters(node)
429
+ delimiter_options = {}
430
+ Delimiter::DELIMITER_TERMS.each do |dt|
431
+ delimiter_options[dt] = attr(node, dt)
432
+ end
433
+ delimiter_options
434
+ end
435
+
436
+ # Shortcut methods for extracting values from the DOM
437
+ def attr(node, attribute_name, namespace = nil)
438
+ attr = (attribute_name.is_a? Symbol) ? attribute_name.to_s : attribute_name
439
+ node.attribute(attr, namespace).value if node.attribute(attr)
440
+ end
441
+
442
+ def text(node, child_node_name = nil)
443
+ if child_node_name
444
+ child = (child_node_name.is_a? Symbol) ? child_node_name.to_s : child_node_name
445
+ # What if there are multiple children here?
446
+ node.elements[child].text if node.elements[child] and node.elements[child].size == 1
447
+ else
448
+ node.text
449
+ end
450
+ end
451
+
452
+ def array(node, child_node_name)
453
+ child = (child_node_name.is_a? Symbol) ? child_node_name.to_s : child_node_name
454
+ results = []
455
+ node.elements[child].each {|c| results << c } if node.elements[child]
456
+ results
457
+ end
458
+
459
+ def time(node, child_node_name)
460
+ require 'time'
461
+ t = text(node, child_node_name)
462
+ Time.parse(t) if t
463
+ end
464
+
465
+ def info_text(node, child_node_name)
466
+ text = text(node, child_node_name)
467
+ lang = attr(node, "lang", "xml")
468
+ InfoText.new(text, lang) if text
469
+ end
470
+ end
471
+ end
472
+
473
+ # Provide a command-line interface to the parser
474
+ if ($FILE == $0)
475
+ if $1
476
+ file = $1
477
+ parser = CSL::CslParser.new(file)
478
+ end
479
+ end