From de5995d62fa85b03173570c0e21fac80efe1ccd1 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Fri, 6 Jan 2017 17:43:34 -0500 Subject: [PATCH] LP#1638299: improve extraction of headings from authority records This patch sets up configuration tables, seed data, and functions for extracting headings from authority records based on (usually) the MARCXML to MADS XSLT. Signed-off-by: Galen Charlton Signed-off-by: Mike Rylander Signed-off-by: Galen Charlton Signed-off-by: Kathy Lussier --- Open-ILS/examples/fm_IDL.xml | 27 + .../sql/Pg/upgrade/XXXX.data.MADS21-xsl.sql | 4 + .../sql/Pg/upgrade/YYYY.schema.authority.sql | 503 ++++++++++++++++++ .../admin/server/authority/heading_field.tt2 | 40 ++ .../templates/staff/admin/server/t_splash.tt2 | 1 + Open-ILS/tests/datasets/sql/auth_lc.sql | 196 +++++++ .../admin/server/authority/heading_field.js | 79 +++ 7 files changed, 850 insertions(+) create mode 100644 Open-ILS/src/sql/Pg/upgrade/YYYY.schema.authority.sql create mode 100644 Open-ILS/src/templates/staff/admin/server/authority/heading_field.tt2 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/server/authority/heading_field.js diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 77871ff426..f96d39d4b1 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2490,6 +2490,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -2498,6 +2499,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -2686,6 +2688,31 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.MADS21-xsl.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.MADS21-xsl.sql index 225be32e52..a2e8e8e16b 100644 --- a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.MADS21-xsl.sql +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.MADS21-xsl.sql @@ -1,3 +1,5 @@ +BEGIN; + INSERT INTO config.xml_transform (name,namespace_uri,prefix,xslt) VALUES ('mads21','http://www.loc.gov/mads/v2','mads21',$XSLT$ $XSLT$); + +COMMIT; diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.authority.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.authority.sql new file mode 100644 index 0000000000..584912ee39 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.schema.authority.sql @@ -0,0 +1,503 @@ +BEGIN; + +-- subset of types listed in https://www.loc.gov/marc/authority/ad1xx3xx.html +-- for now, ignoring subdivisions +CREATE TYPE authority.heading_type AS ENUM ( + 'personal_name', + 'corporate_name', + 'meeting_name', + 'uniform_title', + 'named_event', + 'chronological_term', + 'topical_term', + 'geographic_name', + 'genre_form_term', + 'medium_of_performance_term' +); + +CREATE TYPE authority.variant_heading_type AS ENUM ( + 'abbreviation', + 'acronym', + 'translation', + 'expansion', + 'other', + 'hidden' +); + +CREATE TYPE authority.related_heading_type AS ENUM ( + 'earlier', + 'later', + 'parent organization', + 'broader', + 'narrower', + 'equivalent', + 'other' +); + +CREATE TYPE authority.heading_purpose AS ENUM ( + 'main', + 'variant', + 'related' +); + +CREATE TABLE authority.heading_field ( + id SERIAL PRIMARY KEY, + heading_type authority.heading_type NOT NULL, + heading_purpose authority.heading_purpose NOT NULL, + label TEXT NOT NULL, + format TEXT NOT NULL REFERENCES config.xml_transform (name) DEFAULT 'mads21', + heading_xpath TEXT NOT NULL, + component_xpath TEXT NOT NULL, + type_xpath TEXT NULL, -- to extract related or variant type + thesaurus_xpath TEXT NULL, + thesaurus_override_xpath TEXT NULL, + joiner TEXT NULL +); + +CREATE TABLE authority.heading_field_norm_map ( + id SERIAL PRIMARY KEY, + field INT NOT NULL REFERENCES authority.heading_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + norm INT NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + params TEXT, + pos INT NOT NULL DEFAULT 0 +); + +INSERT INTO authority.heading_field(heading_type, heading_purpose, label, heading_xpath, component_xpath, type_xpath, thesaurus_xpath, thesaurus_override_xpath) VALUES + ( 'topical_term', 'main', 'Main Topical Term', '/mads21:mads/mads21:authority', '//mads21:topic', NULL, '/mads21:mads/mads21:authority/mads21:topic[1]/@authority', NULL ) +,( 'topical_term', 'variant', 'Variant Topical Term', '/mads21:mads/mads21:variant', '//mads21:topic', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:topic[1]/@authority', '//mads21:topic[1]/@authority') +,( 'topical_term', 'related', 'Related Topical Term', '/mads21:mads/mads21:related', '//mads21:topic', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:topic[1]/@authority', '//mads21:topic[1]/@authority') +,( 'personal_name', 'main', 'Main Personal Name', '/mads21:mads/mads21:authority', '//mads21:name[@type="personal"]', NULL, NULL, NULL ) +,( 'personal_name', 'variant', 'Variant Personal Name', '/mads21:mads/mads21:variant', '//mads21:name[@type="personal"]', NULL, NULL, NULL ) +,( 'personal_name', 'related', 'Related Personal Name', '/mads21:mads/mads21:related', '//mads21:name[@type="personal"]', '/mads21:related/@type', NULL, NULL ) +,( 'corporate_name', 'main', 'Main Corporate name', '/mads21:mads/mads21:authority', '//mads21:name[@type="corporate"]', NULL, NULL, NULL ) +,( 'corporate_name', 'variant', 'Variant Corporate Name', '/mads21:mads/mads21:variant', '//mads21:name[@type="corporate"]', NULL, NULL, NULL ) +,( 'corporate_name', 'related', 'Related Corporate Name', '/mads21:mads/mads21:related', '//mads21:name[@type="corporate"]', '/mads21:related/@type', NULL, NULL ) +,( 'meeting_name', 'main', 'Main Meeting name', '/mads21:mads/mads21:authority', '//mads21:name[@type="conference"]', NULL, NULL, NULL ) +,( 'meeting_name', 'variant', 'Variant Meeting Name', '/mads21:mads/mads21:variant', '//mads21:name[@type="conference"]', NULL, NULL, NULL ) +,( 'meeting_name', 'related', 'Related Meeting Name', '/mads21:mads/mads21:related', '//mads21:name[@type="meeting"]', '/mads21:related/@type', NULL, NULL ) +,( 'geographic_name', 'main', 'Main Geographic Term', '/mads21:mads/mads21:authority', '//mads21:geographic', NULL, '/mads21:mads/mads21:authority/mads21:geographic[1]/@authority', NULL ) +,( 'geographic_name', 'variant', 'Variant Geographic Term', '/mads21:mads/mads21:variant', '//mads21:geographic', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:geographic[1]/@authority', '//mads21:geographic[1]/@authority') +,( 'geographic_name', 'related', 'Related Geographic Term', '/mads21:mads/mads21:related', '//mads21:geographic', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:geographic[1]/@authority', '//mads21:geographic[1]/@authority') +,( 'genre_form_term', 'main', 'Main Genre/Form Term', '/mads21:mads/mads21:authority', '//mads21:genre', NULL, '/mads21:mads/mads21:authority/mads21:genre[1]/@authority', NULL ) +,( 'genre_form_term', 'variant', 'Variant Genre/Form Term', '/mads21:mads/mads21:variant', '//mads21:genre', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:genre[1]/@authority', '//mads21:genre[1]/@authority') +,( 'genre_form_term', 'related', 'Related Genre/Form Term', '/mads21:mads/mads21:related', '//mads21:genre', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:genre[1]/@authority', '//mads21:genre[1]/@authority') +,( 'chronological_term', 'main', 'Main Chronological Term', '/mads21:mads/mads21:authority', '//mads21:temporal', NULL, '/mads21:mads/mads21:authority/mads21:temporal[1]/@authority', NULL ) +,( 'chronological_term', 'variant', 'Variant Chronological Term', '/mads21:mads/mads21:variant', '//mads21:temporal', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:temporal[1]/@authority', '//mads21:temporal[1]/@authority') +,( 'chronological_term', 'related', 'Related Chronological Term', '/mads21:mads/mads21:related', '//mads21:temporal', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:temporal[1]/@authority', '//mads21:temporal[1]/@authority') +,( 'uniform_title', 'main', 'Main Uniform Title', '/mads21:mads/mads21:authority', '//mads21:title', NULL, '/mads21:mads/mads21:authority/mads21:title[1]/@authority', NULL ) +,( 'uniform_title', 'variant', 'Variant Uniform Title', '/mads21:mads/mads21:variant', '//mads21:title', '/mads21:variant/@type', '/mads21:mads/mads21:authority/mads21:title[1]/@authority', '//mads21:title[1]/@authority') +,( 'uniform_title', 'related', 'Related Uniform Title', '/mads21:mads/mads21:related', '//mads21:title', '/mads21:related/@type', '/mads21:mads/mads21:authority/mads21:title[1]/@authority', '//mads21:title[1]/@authority') +; + +-- NACO normalize all the things +INSERT INTO authority.heading_field_norm_map (field, norm, pos) +SELECT id, 1, 0 +FROM authority.heading_field; + +CREATE TYPE authority.heading AS ( + field INT, + type authority.heading_type, + purpose authority.heading_purpose, + variant_type authority.variant_heading_type, + related_type authority.related_heading_type, + thesaurus TEXT, + heading TEXT, + normalized_heading TEXT +); + +CREATE OR REPLACE FUNCTION authority.extract_headings(marc TEXT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ +DECLARE + idx authority.heading_field%ROWTYPE; + xfrm config.xml_transform%ROWTYPE; + prev_xfrm TEXT; + transformed_xml TEXT; + heading_node TEXT; + heading_node_list TEXT[]; + component_node TEXT; + component_node_list TEXT[]; + raw_text TEXT; + normalized_text TEXT; + normalizer RECORD; + curr_text TEXT; + joiner TEXT; + type_value TEXT; + base_thesaurus TEXT := NULL; + output_row authority.heading; +BEGIN + + -- Loop over the indexing entries + FOR idx IN SELECT * FROM authority.heading_field WHERE restrict IS NULL OR id = ANY (restrict) ORDER BY format LOOP + + output_row.field := idx.id; + output_row.type := idx.heading_type; + output_row.purpose := idx.heading_purpose; + + joiner := COALESCE(idx.joiner, ' '); + + SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format; + + -- See if we can skip the XSLT ... it's expensive + IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN + -- Can't skip the transform + IF xfrm.xslt <> '---' THEN + transformed_xml := oils_xslt_process(marc, xfrm.xslt); + ELSE + transformed_xml := marc; + END IF; + + prev_xfrm := xfrm.name; + END IF; + + IF idx.thesaurus_xpath IS NOT NULL THEN + base_thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + END IF; + + heading_node_list := oils_xpath( idx.heading_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + + FOR heading_node IN SELECT x FROM unnest(heading_node_list) AS x LOOP + + CONTINUE WHEN heading_node !~ E'^\\s*<'; + + output_row.variant_type := NULL; + output_row.related_type := NULL; + output_row.thesaurus := NULL; + output_row.heading := NULL; + + IF idx.heading_purpose = 'variant' AND idx.type_xpath IS NOT NULL THEN + type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + BEGIN + output_row.variant_type := type_value; + EXCEPTION WHEN invalid_text_representation THEN + RAISE NOTICE 'Do not recognize variant heading type %', type_value; + END; + END IF; + IF idx.heading_purpose = 'related' AND idx.type_xpath IS NOT NULL THEN + type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + BEGIN + output_row.related_type := type_value; + EXCEPTION WHEN invalid_text_representation THEN + RAISE NOTICE 'Do not recognize related heading type %', type_value; + END; + END IF; + + IF idx.thesaurus_override_xpath IS NOT NULL THEN + output_row.thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_override_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), ''); + END IF; + IF output_row.thesaurus IS NULL THEN + output_row.thesaurus := base_thesaurus; + END IF; + + raw_text := NULL; + + -- now iterate over components of heading + component_node_list := oils_xpath( idx.component_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] ); + FOR component_node IN SELECT x FROM unnest(component_node_list) AS x LOOP + -- XXX much of this should be moved into oils_xpath_string... + curr_text := ARRAY_TO_STRING(evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value( + oils_xpath( '//text()', -- get the content of all the nodes within the main selected node + REGEXP_REPLACE( component_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space + ), ' '), ''), -- throw away morally empty (bankrupt?) strings + joiner + ); + + CONTINUE WHEN curr_text IS NULL OR curr_text = ''; + + IF raw_text IS NOT NULL THEN + raw_text := raw_text || joiner; + END IF; + + raw_text := COALESCE(raw_text,'') || curr_text; + END LOOP; + + IF raw_text IS NOT NULL THEN + output_row.heading := raw_text; + normalized_text := raw_text; + + FOR normalizer IN + SELECT n.func AS func, + n.param_count AS param_count, + m.params AS params + FROM config.index_normalizer n + JOIN authority.heading_field_norm_map m ON (m.norm = n.id) + WHERE m.field = idx.id + ORDER BY m.pos LOOP + + EXECUTE 'SELECT ' || normalizer.func || '(' || + quote_literal( normalized_text ) || + CASE + WHEN normalizer.param_count > 0 + THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'') + ELSE '' + END || + ')' INTO normalized_text; + + END LOOP; + + output_row.normalized_heading := normalized_text; + + RETURN NEXT output_row; + END IF; + END LOOP; + + END LOOP; +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION authority.extract_headings(rid BIGINT, restrict INT[] DEFAULT NULL) RETURNS SETOF authority.heading AS $func$ +DECLARE + auth authority.record_entry%ROWTYPE; + output_row authority.heading; +BEGIN + -- Get the record + SELECT INTO auth * FROM authority.record_entry WHERE id = rid; + + RETURN QUERY SELECT * FROM authority.extract_headings(auth.marc, restrict); +END; +$func$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$ +DECLARE + res authority.simple_heading%ROWTYPE; + acsaf authority.control_set_authority_field%ROWTYPE; + heading_row authority.heading%ROWTYPE; + tag_used TEXT; + nfi_used TEXT; + sf TEXT; + cset INT; + heading_text TEXT; + joiner_text TEXT; + sort_text TEXT; + tmp_text TEXT; + tmp_xml TEXT; + first_sf BOOL; + auth_id INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; +BEGIN + + SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id; + + IF cset IS NULL THEN + SELECT control_set INTO cset + FROM authority.control_set_authority_field + WHERE tag IN ( SELECT UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[])) + LIMIT 1; + END IF; + + res.record := auth_id; + res.thesaurus := authority.extract_thesaurus(marcxml); + + FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP + res.atag := acsaf.id; + + IF acsaf.heading_field IS NULL THEN + tag_used := acsaf.tag; + nfi_used := acsaf.nfi; + joiner_text := COALESCE(acsaf.joiner, ' '); + + FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)::TEXT[]) LOOP + + heading_text := COALESCE( + oils_xpath_string('./*[contains("'||acsaf.display_sf_list||'",@code)]', tmp_xml, joiner_text), + '' + ); + + IF nfi_used IS NOT NULL THEN + + sort_text := SUBSTRING( + heading_text FROM + COALESCE( + NULLIF( + REGEXP_REPLACE( + oils_xpath_string('./@ind'||nfi_used, tmp_xml::TEXT), + $$\D+$$, + '', + 'g' + ), + '' + )::INT, + 0 + ) + 1 + ); + + ELSE + sort_text := heading_text; + END IF; + + IF heading_text IS NOT NULL AND heading_text <> '' THEN + res.value := heading_text; + res.sort_value := public.naco_normalize(sort_text); + res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value); + RETURN NEXT res; + END IF; + + END LOOP; + ELSE + FOR heading_row IN SELECT * FROM authority.extract_headings(marcxml, ARRAY[acsaf.heading_field]) LOOP + res.value := heading_row.heading; + res.sort_value := heading_row.normalized_heading; + res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value); + RETURN NEXT res; + END LOOP; + END IF; + END LOOP; + + RETURN; +END; +$func$ LANGUAGE PLPGSQL STABLE STRICT; + +ALTER TABLE authority.control_set_authority_field ADD COLUMN heading_field INTEGER REFERENCES authority.heading_field(id); + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '100' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'personal_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '400' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'personal_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '500' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'personal_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '110' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'corporate_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '410' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'corporate_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '510' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'corporate_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '111' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'meeting_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '411' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'meeting_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '511' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'meeting_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '130' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'uniform_title'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '430' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'uniform_title'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '530' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'uniform_title'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '150' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'topical_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '450' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'topical_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '550' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'topical_term'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '151' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'geographic_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '451' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'geographic_name'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '551' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'geographic_name'; + +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '155' +AND control_set = 1 +AND ahf.heading_purpose = 'main' +AND ahf.heading_type = 'genre_form_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '455' +AND control_set = 1 +AND ahf.heading_purpose = 'variant' +AND ahf.heading_type = 'genre_form_term'; +UPDATE authority.control_set_authority_field acsaf +SET heading_field = ahf.id +FROM authority.heading_field ahf +WHERE tag = '555' +AND control_set = 1 +AND ahf.heading_purpose = 'related' +AND ahf.heading_type = 'genre_form_term'; + +COMMIT; diff --git a/Open-ILS/src/templates/staff/admin/server/authority/heading_field.tt2 b/Open-ILS/src/templates/staff/admin/server/authority/heading_field.tt2 new file mode 100644 index 0000000000..51a7047d2c --- /dev/null +++ b/Open-ILS/src/templates/staff/admin/server/authority/heading_field.tt2 @@ -0,0 +1,40 @@ +[% + WRAPPER "staff/base.tt2"; + ctx.page_title = l("Authority Heading Fields"); + ctx.page_app = "egAdminConfig"; + ctx.page_ctrl = 'AuthorityHeadingField'; +%] + +[% BLOCK APP_JS %] + + + + + +[% END %] + +
+
+ [% l('Authority Heading Fields') %] +
+
+ + + + + + + + + + + + + + +[% END %] diff --git a/Open-ILS/src/templates/staff/admin/server/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/server/t_splash.tt2 index 36faae3d98..44013fea52 100644 --- a/Open-ILS/src/templates/staff/admin/server/t_splash.tt2 +++ b/Open-ILS/src/templates/staff/admin/server/t_splash.tt2 @@ -14,6 +14,7 @@ ,[ l('Asset Stat Cat Sip Fields'), "./admin/server/config/asset_sip_fields" ] ,[ l('Authority Browse Axes'), "./admin/server/cat/authority/browse_axis" ] ,[ l('Authority Control Sets'), "./admin/server/cat/authority/control_set" ] + ,[ l('Authority Heading Fields'), "./admin/server/authority/heading_field" ] ,[ l('Authority Thesauri'), "./admin/server/cat/authority/thesaurus" ] ,[ l('Best-Hold Selection Sort Order'), "./admin/server/config/best_hold_order" ] ,[ l('Billing Types'), "./admin/server/config/billing_type" ] diff --git a/Open-ILS/tests/datasets/sql/auth_lc.sql b/Open-ILS/tests/datasets/sql/auth_lc.sql index 21f300f3ed..78ac249ad8 100644 --- a/Open-ILS/tests/datasets/sql/auth_lc.sql +++ b/Open-ILS/tests/datasets/sql/auth_lc.sql @@ -1074,3 +1074,199 @@ INSERT INTO authority.record_entry(marc, last_xact_id) VALUES ($$Puppies $$, :xact_id); +INSERT INTO authority.record_entry(marc, last_xact_id) VALUES ($$ + 04524cz a2200769n 4500 + sh85082617 + DLC + 20151028130522.2 + 860211|| anannbabn |a ana + + sh 85082617 + + + W1510027 + W1510027 + N0630410 + N0630410 + geonames + + + DLC + DLC + DLC + + + Denali, Mount (Alaska) + + + Bolshoy Mountain (Alaska) + + + Bulshaia Gora (Alaska) + + + Bulshaya Gora (Alaska) + + + Bulshoe (Alaska) + + + Churchill Peaks (Alaska) + + + Deenaalee Mountain (Alaska) + + + Deenadhee (Alaska) + + + Deenadheet (Alaska) + + + Deenalee (Alaska) + + + Deghilaay Ce'e (Alaska) + + + Deghilaay Ke'e (Alaska) + + + Delaykah (Alaska) + + + Denadhe (Alaska) + + + Denagadh (Alaska) + + + Denaze (Alaska) + + + Dengadh (Alaska) + + + Dengadhe (Alaska) + + + Dengadhi (Alaska) + + + Dengadhiy (Alaska) + + + Densmore's Mountain (Alaska) + + + Densmores Peak (Alaska) + + + Dghelaay Ce'e (Alaska) + + + Dghelaay Ke'e (Alaska) + + + Dghelay Ka'a (Alaska) + + + Dghili Ka'a (Alaska) + + + Diinaadhi (Alaska) + + + Diinaadhii (Alaska) + + + Diinaadhiit (Alaska) + + + Diinaalii (Alaska) + + + Diinaazii (Alaska) + + + Diineezi (Alaska) + + + Din-al-ee (Alaska : Mount Denali) + + + Din-az-ee (Alaska : Mount Denali) + + + Doleika (Alaska) + + + Doleyka (Alaska) + + + nne + McKinley, Mount (Alaska) + + + Mount Denali (Alaska) + + + Mount Doleika (Alaska) + + + Mount McKinley (Alaska) + + + North Peak (Alaska : Mount Denali) + + + South Peak (Alaska : Mount Denali) + + + Tenada (Alaska) + + + Tenda (Alaska) + + + Tennaly (Alaska) + + + To-lah-gah (Alaska) + + + Traleika (Alaska : Mount Denali) + + + Traleyka (Alaska) + + + g + Alaska Range (Alaska) + + + g + Mountains + Alaska + + + GeoNames [algorithmically matched] + summit; 63°04ʹ10ʺN 151°00ʹ27ʺW + + + GNIS, Aug. 31, 2015: + (Denali; summit in Denali County, Alaska; variant names: Bolshoy; Bulshaia Gora; Bulshaya Gora; Bulshoe; Churchill Peaks; Deenaalee; Deenadhee; Deenadheet; Deenalee; Deghilaay Ce'e; Deghilaay Ke'e; Delaykah; Denadhe; Denagadh; Denaze; Dengadh; Dengadhe; Dengadhi; Dengadhiy; Densmore's Mountain; Densmores Peak; Dghelaay Ce'e; Dghelaay Ke'e; Dghelay Ka'a; Dghili Ka'a; Diinaadhi; Diinaadhii; Diinaadhiit; Diinaalii; Diinaazii; Diineezi; Din-al-ee; Din-az-ee; Doleika; Doleyka; Mount Denali; Mount Doleika; Mount McKinley; North Peak; North Peak Mount McKinley; South Peak; South Peak Mount McKinley; Tenada; Tenda; Tennaly; To-lah-gah; Traleika; Traleyka; Denali also name of ppl and several other jurisdictions in Alaska; Bolshoy also name of several islands; Deenaalee also name of lake; Din-al-ee and Din-az-ee also variant names for Mount Foraker in Denali County; Traleika also name of summit in "Alaska Range near Mount McKinley" [no county listed]; North Peak and South Peak also names for other summits in other counties in Alaska) + + + Washington Post online, Aug. 31, 2015: + (article: Obama will rename the highest U.S. peak, dropping McKinley for Denali; President Obama in Anchorage on Monday will announce the renaming of Mount McKinley, honoring the 25th president, to Mount Denali, an Athabascan name used by generations of Alaska Natives that means "the great one.") + + + Columbia gazetteer online, Aug. 31, 2015: + (McKinley, Mount or Denali (=the great one), mountain (20,320 ft/6,194 m), S central Alaska, in the Alaska Range; 63°04ʹN 151°00ʹW. Highest point in North America. McKinley features two main peaks: the higher is South Peak (20,320 ft/6,194 m) and North Peak (19,470 ft/5,934 m); the two together have sometimes been known as the Churchhill Peaks. Other notable peaks on the mountain are South Buttress (15,885 ft/4,842 m), East Buttress (14,730 ft/4,490 m), and Browne Tower (14,530 ft/4,429m). Permanent snowfields cover more than half the mountain and feed numerous glaciers. Mount McKinley was first scaled successfully by the American explorer Hudson Stuck in 1913. It is included in Denali National Park and Preserve.) + + + Alaska + Denali, Mount + +$$, :xact_id); diff --git a/Open-ILS/web/js/ui/default/staff/admin/server/authority/heading_field.js b/Open-ILS/web/js/ui/default/staff/admin/server/authority/heading_field.js new file mode 100644 index 0000000000..1542219eb6 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/admin/server/authority/heading_field.js @@ -0,0 +1,79 @@ +angular.module('egAdminConfig', + ['ngRoute','ui.bootstrap','egCoreMod','egUiMod','egGridMod','egFmRecordEditorMod']) + +.controller('AuthorityHeadingField', + ['$scope','$q','$timeout','$location','$window','$uibModal','egCore','egGridDataProvider', + 'egConfirmDialog', +function($scope , $q , $timeout , $location , $window , $uibModal , egCore , egGridDataProvider , + egConfirmDialog) { + + egCore.startup.go(); // standalone mode requires manual startup + + $scope.new_record = function() { + spawn_editor(); + } + + $scope.edit_record = function(items) { + if (items.length != 1) return; + spawn_editor(items[0].id); + } + + spawn_editor = function(id) { + var templ; + if (arguments.length == 1) { + templ = ''; + } else { + templ = ''; + } + gridControls = $scope.gridControls; + $uibModal.open({ + template : templ, + controller : [ + '$scope', '$uibModalInstance', + function($scope , $uibModalInstance) { + $scope.id = id; + + $scope.ok = function($event) { + $uibModalInstance.close(); + gridControls.refresh(); + } + + $scope.cancel = function($event) { + $uibModalInstance.dismiss(); + } + } + ] + }); + } + + $scope.delete_record = function(selected) { + if (!selected || !selected.length) return; + + egCore.pcrud.retrieve('ahf', selected[0].id).then(function(rec) { + egConfirmDialog.open( + egCore.strings.EG_CONFIRM_DELETE_RECORD_TITLE, + egCore.strings.EG_CONFIRM_DELETE_RECORD_BODY, + { id : rec.id() } // TODO replace with selector if available? + ).result.then(function() { + egCore.pcrud.remove(rec).then(function() { + $scope.gridControls.refresh(); + }); + }); + }); + } + + function generateQuery() { + return { + 'id' : { '!=' : null } + } + } + + $scope.gridControls = { + setQuery : function() { + return generateQuery(); + }, + setSort : function() { + return ['heading_type','heading_purpose']; + } + } +}]) -- 2.43.2