Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/datura/data_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def self.format_to_class
"csv" => FileCsv,
"ead" => FileEad,
"html" => FileHtml,
"personography" => FilePersonography,
"tei" => FileTei,
"vra" => FileVra,
"webs" => FileWebs,
Expand Down
79 changes: 79 additions & 0 deletions lib/datura/file_types/file_personography.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require_relative "../helpers.rb"
require_relative "../file_type.rb"
require_relative "../solr_poster.rb"
require_relative "file_tei.rb"
require "rest-client"
require "json"

# FilePersonography handles TEI personography files — XML files containing
# a <listPerson> element with individual <person> children, each identified
# by a unique xml:id attribute.
#
# Unlike standard TEI processing (one ES document per file), this class
# produces one ES JSON document per <person> element, with each output file
# named by the person's xml:id value (e.g., hickok.j.json).
#
# HTML output uses the standard tei_to_html XSLT, which via
# personography_encyclopedia.xsl writes:
# - one combined HTML file with all persons as divs (served at /item/{filename})
# - individual HTML files per person via xsl:result-document (served at /item/{xml:id})
#
# Solr output uses the standard tei_to_solr XSLT, which via
# tei_to_solr/lib/personography.xsl produces per-person Solr docs.
#
# Source files should be placed in source/personography/ within the collection.
class FilePersonography < FileTei

# Maps each <person> element to TeiToEsPersonography for ES transformation.
# The parent /TEI document is intentionally excluded — one ES doc is produced
# per person, not one for the entire file.
def subdoc_xpaths
{ "//listPerson/person" => TeiToEsPersonography }
end

# Overrides FileType#transform_es to write one JSON file per person
# (named by the person's xml:id) instead of a single file for the whole document.
# Returns an array of JSON hashes so that post_es can iterate and POST them
# to Elasticsearch without modification.
def transform_es
es_req = []
begin
file_xml = parse_markup_lang_file

# Verify at least one person element exists before continuing
results = file_xml.xpath(*subdoc_xpaths.keys)
if results.length == 0
raise "No persons found in #{self.filename}; verify //listPerson/person matches the XML structure"
end

file_xml.xpath("//listPerson/person").each do |person_node|
transformer = TeiToEsPersonography.new(person_node, @options, file_xml, self.filename(false))
person_json = transformer.json
es_req << person_json

# Write one JSON file per person using the person's identifier as the filename.
# Produces e.g. output/es/hickok.j.json rather than output/es/wfc.person.json.
if @options["output"]
person_id = person_json["identifier"]
if person_id.nil? || person_id.empty?
puts "WARNING: person in #{self.filename} has no identifier; skipping ES file output"
next
end
filepath = File.join(@out_es, "#{person_id}.json")
File.open(filepath, "w") { |f| f.write(JSON.pretty_generate(person_json)) }
end
end

return es_req
rescue => e
puts "Error transforming #{self.filename}: #{e}"
puts e.backtrace
raise e
end
end

def transform_iiif
raise "Personography to IIIF is not implemented"
end

end
89 changes: 72 additions & 17 deletions lib/datura/to_es/tei_to_es/tei_to_es_personography.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,57 @@
# TeiToEsPersonography transforms a single <person> element from a TEI
# personography file into an Elasticsearch JSON document.
#
# Each <person> element becomes one ES document, identified by the person's
# xml:id attribute value (e.g., "hickok.j"). The parent TEI document is passed
# as @parent_xml so that header-level fields (creator, contributor, etc.) can
# still be extracted from the full file.

class TeiToEsPersonography < TeiToEs

def override_xpaths
{
# Merges person-level xpaths on top of the standard TeiToEs xpaths, then
# merges override_xpaths on top so collection teams can still customize using
# the familiar hook pattern (e.g., in scripts/overrides/tei_to_es_personography.rb).
# Paths here are relative to the <person> element (@xml), not the document root.
# Header-level fields (creator, contributor, rights, etc.) are unchanged and
# are queried against @parent_xml in the field methods below.
def xpaths_list
super.merge({
"titles" => {
"main" => "persName[@type='display']",
"alt" => "persName"
"alt" => "persName"
},
"text" => "note"
}
"text" => "note",
"birth_year" => "birth/@when",
"death_year" => "death/@when"
}).merge(override_xpaths)
end

# No-op by default; override in a collection script to add or replace xpaths.
def override_xpaths
{}
end

# Returns the person's xml:id value as the document identifier.
# After namespace removal by Nokogiri, xml:id is accessible as the "id" attribute.
def get_id
@xml["id"]
end

def category
"Life"
end

def creator
creators = get_list(@xpaths["creators"], false, @parent_xml)
if creators
creators.map { |c| { "name" => c } }
end
# Hardcoded subcategory for all personography entries.
def subcategory
"Personography"
end

def creators
get_text(@xpaths["creators"], false, @parent_xml)
def creator
[]
end

def get_id
person = @xml["id"]
"#{@filename}_#{person}"
def creators
nil
end

def person
Expand All @@ -38,8 +62,39 @@ def person
}]
end

def subcategory
"Personography"
# Uses birth/@when as the person's primary date (standardized to start of year).
def date
year = get_text(@xpaths["birth_year"])
Datura::Helpers.date_standardize(year, true) if year
end

# Same as date — birth year as the not-before bound.
def date_not_before
year = get_text(@xpaths["birth_year"])
Datura::Helpers.date_standardize(year, true) if year
end

# Uses death/@when as the not-after date (standardized to end of year).
def date_not_after
year = get_text(@xpaths["death_year"])
Datura::Helpers.date_standardize(year, false) if year
end

# Raw birth year string for display (e.g., "1850").
def date_display
get_text(@xpaths["birth_year"])
end

# Points to the source personography XML file (the whole file, not per-person),
# since all persons share one source document.
def uri_data
File.join(
@options["data_base"],
"data",
@options["collection"],
"source/personography",
"#{@filename}.xml"
)
end

end
2 changes: 1 addition & 1 deletion lib/xslt/tei_to_html/lib/personography_encyclopedia.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<!-- this is the same as the above but written to a specific file -->
<xsl:for-each select="//person">
<!-- the filename will start relative to the html-generated (output) directory of a specific collection -->
<xsl:variable name="filename" select="concat('collections/', $collection, '/output/', $environment, '/html/', @xml:id, '.html')"/>
<xsl:variable name="filename" select="concat('output/', $environment, '/html/', @xml:id, '.html')"/>
<xsl:result-document href="{$filename}">
<xsl:call-template name="person_info"/>
</xsl:result-document>
Expand Down