From 34e16b7d7a88013578014be0402ab686b03f235b Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Tue, 1 Jul 2025 08:40:49 +0200 Subject: lib: Introduce the PlainExporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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à --- app/controllers/exports_controller.rb | 6 +++++ app/views/exports/new.html.erb | 1 + lib/base_exporter.rb | 42 +++++++++++++++++++++++++++++ lib/plain_exporter.rb | 42 +++++++++++++++++++++++++++++ lib/uoc_exporter.rb | 40 +++++---------------------- test/controllers/exports_controller_test.rb | 17 ++++++++++++ test/fixtures/files/all.txt | 2 ++ 7 files changed, 117 insertions(+), 33 deletions(-) create mode 100644 lib/plain_exporter.rb create mode 100644 test/fixtures/files/all.txt 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 @@ <%= I18n.t('searches.export.action').capitalize %> 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. -- cgit v1.2.3