aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-07-01 08:40:49 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-07-01 08:53:56 +0200
commit34e16b7d7a88013578014be0402ab686b03f235b (patch)
tree5ef848d3c44f72ff95a24b123e5da88bc5aef1a2
parent24714068efc5866999311ad482d54f5c7a90c449 (diff)
downloadoperum-34e16b7d7a88013578014be0402ab686b03f235b.tar.gz
operum-34e16b7d7a88013578014be0402ab686b03f235b.zip
lib: Introduce the PlainExporter
This class exports things into plain text. It follows a close format to that of the one for UOC, but without any formatting. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
-rw-r--r--app/controllers/exports_controller.rb6
-rw-r--r--app/views/exports/new.html.erb1
-rw-r--r--lib/base_exporter.rb42
-rw-r--r--lib/plain_exporter.rb42
-rw-r--r--lib/uoc_exporter.rb40
-rw-r--r--test/controllers/exports_controller_test.rb17
-rw-r--r--test/fixtures/files/all.txt2
7 files changed, 117 insertions, 33 deletions
diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb
index 4303ea6..812cf39 100644
--- a/app/controllers/exports_controller.rb
+++ b/app/controllers/exports_controller.rb
@@ -8,6 +8,12 @@ class ExportsController < ApplicationController
respond_to do |format|
format.csv { set_file_name!(ext: 'csv') }
format.uoc { set_file_name!(ext: 'tex') }
+ format.text do
+ send_data PlainExporter.new(things: @results).export,
+ type: 'text/plain',
+ disposition: 'attachment',
+ filename: "#{@search.name}.txt"
+ end
end
end
diff --git a/app/views/exports/new.html.erb b/app/views/exports/new.html.erb
index 631da20..fa52360 100644
--- a/app/views/exports/new.html.erb
+++ b/app/views/exports/new.html.erb
@@ -11,6 +11,7 @@
<select id="export-select-format" data-action="export#change">
<option selected="selected" value="csv">CSV</option>
<option value="uoc">UOC</option>
+ <option value="txt">Plain text</option>
</select>
<a id="export-format-button" class="button" href="#"><%= I18n.t('searches.export.action').capitalize %></a>
diff --git a/lib/base_exporter.rb b/lib/base_exporter.rb
index b040592..f64301b 100644
--- a/lib/base_exporter.rb
+++ b/lib/base_exporter.rb
@@ -5,6 +5,8 @@ class BaseExporter
@things = things
end
+ protected
+
# Returns a string which forces a new TeX line.
def tex_new_line
'\\\\~\\\\'
@@ -26,4 +28,44 @@ class BaseExporter
[a[0..-2].join(' '), a.last]
end
+
+ # Parse the string in `thing.authors` and put each author followed by
+ # semicolon and the right order for first and last name. It will also cover
+ # whenever editors are involved.
+ def parse_authors(thing:)
+ str = thing.authors.split(',').map do |author|
+ first, last = parse_author(author: author.strip)
+ last.blank? ? upper_case(text: first).to_s : "#{upper_case(text: last)}, #{first}"
+ end.join('; ')
+
+ str += ' (eds.)' if thing.editors
+ str
+ end
+
+ # Returns an "Inside of" string where `sub` is the substring contained in it.
+ def inside_of(sub:)
+ str = if sub.downcase.start_with?(*%w[a e i o u])
+ I18n.t('general.insideof-vowel')
+ else
+ I18n.t('general.insideof')
+ end
+
+ str + " #{emph(text: sub)}"
+ end
+
+ # Returns the head of a formatted "thing", containing authors, year, title
+ # and, if available, the "inside of" information. This is shared across some
+ # exporters, thus placed in here.
+ #
+ # NOTE: the subclass *must* implement `parse_title`.
+ def head(thing:)
+ str = "#{parse_authors(thing:)} (#{thing.year}). #{parse_title(title: thing.title)}."
+
+ if thing.insideof.present?
+ str += " #{inside_of(sub: thing.insideof)}"
+ str += thing.pages.present? ? ", #{thing.pages}." : '.'
+ end
+
+ str
+ end
end
diff --git a/lib/plain_exporter.rb b/lib/plain_exporter.rb
new file mode 100644
index 0000000..514d243
--- /dev/null
+++ b/lib/plain_exporter.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+# Plain text exporter.
+class PlainExporter < BaseExporter
+ def export
+ @things.map { |thing| "#{head(thing:)} #{body(thing:)}".strip }.join("\n")
+ end
+
+ protected
+
+ # Returns the same text as the plain exporter does not have formatting.
+ def emph(text:)
+ text
+ end
+
+ # Returns the same text as the plain exporter does not have formatting.
+ def upper_case(text:)
+ text
+ end
+
+ # Returns the same text with underscores stripped, as the plain exporter does
+ # not have formatting.
+ def parse_title(title:)
+ title.delete('_')
+ end
+
+ # Returns text with information on the publisher, notes, year, address, etc;
+ # if available.
+ def body(thing:)
+ str = ''
+
+ if thing.publisher.present?
+ str += thing.note.present? ? "#{thing.note}. #{thing.publisher}" : thing.publisher
+ end
+
+ if thing.address.present?
+ str + ", #{thing.address}: #{thing.year}."
+ else
+ "#{str}."
+ end
+ end
+end
diff --git a/lib/uoc_exporter.rb b/lib/uoc_exporter.rb
index e8dcc62..9deffc5 100644
--- a/lib/uoc_exporter.rb
+++ b/lib/uoc_exporter.rb
@@ -13,17 +13,14 @@ class UocExporter < BaseExporter
protected
- # Returns the head of a line, containing authors, year, title and, if
- # available, the "inside of" information.
- def head(thing:)
- str = "#{parse_authors(thing:)} (#{thing.year}). #{parse_title(title: thing.title)}."
-
- if thing.insideof.present?
- str += " #{inside_of(thing.insideof)}"
- str += thing.pages.present? ? ", #{thing.pages}." : '.'
- end
+ # Returns the same text with LaTeX \emph applied.
+ def emph(text:)
+ "\\emph{#{text}}"
+ end
- str
+ # Returns the same text with LaTeX \textsc applied.
+ def upper_case(text:)
+ "\\textsc{#{text}}"
end
# Returns the title without underscores and with the expected formatting.
@@ -45,29 +42,6 @@ class UocExporter < BaseExporter
"\\emph{#{str}}"
end
- # Parse the authors as delivered by `thing.authors` and render them in the
- # proper format.
- def parse_authors(thing:)
- str = thing.authors.split(',').map do |author|
- first, last = parse_author(author: author.strip)
- last.blank? ? "\\textsc{#{first}}" : "\\textsc{#{last}}, #{first}"
- end.join('; ')
-
- str += ' (eds.)' if thing.editors
- str
- end
-
- # Returns the proper 'inside of' translated text.
- def inside_of(sub)
- str = if sub.downcase.start_with?(*%w[a e i o u])
- I18n.t('general.insideof-vowel')
- else
- I18n.t('general.insideof')
- end
-
- str + " \\emph{#{sub}}"
- end
-
# Returns the 'body' of the line, which is the rest of it (i.e. note,
# publisher, year).
def body(thing:)
diff --git a/test/controllers/exports_controller_test.rb b/test/controllers/exports_controller_test.rb
index fe94362..0b0ba1d 100644
--- a/test/controllers/exports_controller_test.rb
+++ b/test/controllers/exports_controller_test.rb
@@ -42,4 +42,21 @@ class ExportsControllerTest < ActionDispatch::IntegrationTest
assert_equal @response.body, file_fixture('underscores.uoc.tex').read
end
+
+ test 'plain: works' do
+ get search_exports_url(0, format: 'txt')
+
+ assert_equal @response.body, file_fixture('all.txt').read.strip
+ assert_equal 'text/plain', @response.media_type
+ end
+
+ test 'plain: underscore is removed for the title' do
+ things(:thing1).update!(title: 'This is a _title_ with _many_ underscores')
+
+ get search_exports_url(0, format: 'txt')
+
+ body = @response.body.split("\n")
+
+ assert_includes body.last, 'This is a title with many underscores'
+ end
end
diff --git a/test/fixtures/files/all.txt b/test/fixtures/files/all.txt
new file mode 100644
index 0000000..49205a9
--- /dev/null
+++ b/test/fixtures/files/all.txt
@@ -0,0 +1,2 @@
+Sabaté, Miquel; Nom (eds.) (2024). This is a 2. Dins de Some other thing, 22-24. This is a note 2. Publisher 2, Address 2: 2024.
+Sabaté, Miquel; Smith, John; cognom2, Nom cognom1; cognom3 cognom4, Nom (2024). This is a title 1. Dins de Some other thing, 22-24. This is a note 1. Publisher 1.