LP#1638299: improve extraction of headings from authority records
authorGalen Charlton <gmc@esilibrary.com>
Fri, 6 Jan 2017 22:43:34 +0000 (17:43 -0500)
committerKathy Lussier <klussier@masslnc.org>
Fri, 1 Sep 2017 19:40:07 +0000 (15:40 -0400)
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 <gmc@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/sql/Pg/upgrade/XXXX.data.MADS21-xsl.sql
Open-ILS/src/sql/Pg/upgrade/YYYY.schema.authority.sql [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/server/authority/heading_field.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/server/t_splash.tt2
Open-ILS/tests/datasets/sql/auth_lc.sql
Open-ILS/web/js/ui/default/staff/admin/server/authority/heading_field.js [new file with mode: 0644]

index 77871ff..f96d39d 100644 (file)
@@ -2490,6 +2490,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Thesauri" name="thesauri" reporter:datatype="link" oils_persist:virtual="true"/>
                        <field reporter:label="Browse Axis Maps" name="axis_maps" reporter:datatype="link" oils_persist:virtual="true"/>
                        <field reporter:label="Joiner" name="joiner" reporter:datatype="text" />
+                       <field reporter:label="Heading Field" name="heading_field" reporter:datatype="link" />
                </fields>
                <links>
                        <link field="axis_maps" reltype="has_many" key="field" map="" class="abaafm"/>
@@ -2498,6 +2499,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="thesauri" reltype="has_many" key="control_set" map="" class="at"/>
                        <link field="main_entry" reltype="has_a" key="id" map="" class="acsaf"/>
                        <link field="sub_entries" reltype="has_many" key="main_entry" map="" class="acsaf"/>
+                       <link field="heading" reltype="has_a" key="id" map="" class="ahf"/>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
@@ -2686,6 +2688,31 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="atag" reltype="has_a" key="id" map="" class="acsaf"/>
                </links>
        </class>
+
+       <class id="ahf" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="authority::heading_field" oils_persist:tablename="authority.heading_field" reporter:label="Authority Heading Fields">
+               <fields oils_persist:primary="id" oils_persist:sequence="authority.heading_fields_id_seq">
+                       <field name="id" reporter:datatype="id" />
+                       <field name="heading_type" reporter:datatype="text" reporter:label="Heading Type" />
+                       <field name="heading_purpose" reporter:datatype="text" reporter:label="Heading Purpose" />
+                       <field name="label" reporter:datatype="text" reporter:label="Heading Field Label" />
+                       <field name="format" reporter:datatype="text" reporter:label="Heading XSLT Format" />
+                       <field name="heading_xpath" reporter:datatype="text" reporter:label="Heading XPath" />
+                       <field name="component_xpath" reporter:datatype="text" reporter:label="Heading Component XPath" />
+                       <field name="type_xpath" reporter:datatype="text" reporter:label="Related/Variant Type XPath" />
+                       <field name="thesaurus_xpath" reporter:datatype="text" reporter:label="Thesaurus XPath" />
+                       <field name="thesaurus_override_xpath" reporter:datatype="text" reporter:label="Thesaurus Override XPath" />
+                       <field name="joiner" reporter:datatype="text" reporter:label="Joiner string" />
+               </fields>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="CREATE_AUTHORITY_CONTROL_SET" global_required="true"/>
+                               <retrieve/>
+                               <update permission="UPDATE_AUTHORITY_CONTROL_SET" global_required="true"/>
+                               <delete permission="UPDATE_AUTHORITY_CONTROL_SET" global_required="true"/>
+                       </actions>
+               </permacrud>
+       </class>
+
        <class id="clm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::language_map" oils_persist:tablename="config.language_map" reporter:label="Language Map" oils_persist:field_safe="true">
                <fields oils_persist:primary="code" oils_persist:sequence="">
                        <field reporter:label="Language Code" name="code" reporter:selector="value" reporter:datatype="text"/>
index 225be32..a2e8e8e 100644 (file)
@@ -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$<?xml version="1.0" encoding="UTF-8"?>
 <xsl:stylesheet version="1.0" xmlns:mads="http://www.loc.gov/mads/v2"
        xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:marc="http://www.loc.gov/MARC21/slim"
@@ -1622,3 +1624,5 @@ INSERT INTO config.xml_transform (name,namespace_uri,prefix,xslt) VALUES ('mads2
        </xsl:template>
        <xsl:template match="*"/>
 </xsl:stylesheet>$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 (file)
index 0000000..584912e
--- /dev/null
@@ -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 (file)
index 0000000..51a7047
--- /dev/null
@@ -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 %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/fm_record_editor.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/server/authority/heading_field.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Authority Heading Fields') %]
+  </div>
+</div>
+
+<eg-grid
+    id-field="id"
+    idl-class="ahf"
+    grid-controls="gridControls"
+    features="-multiselect"
+    persist-key="admin.server.config.marc_field">
+
+    <eg-grid-menu-item handler="new_record" label="[% l('New Record') %]"></eg-grid-menu-item>
+    <eg-grid-action handler="edit_record" label="[% l('Edit Record') %]"></eg-grid-action>
+    <eg-grid-action handler="delete_record" label="[% l('Delete Record') %]"></eg-grid-action>
+
+    <eg-grid-field label="[% l('Heading Type') %]"    path="heading_type"></eg-grid-field>
+    <eg-grid-field label="[% l('Heading Purpose') %]" path="heading_purpose"></eg-grid-field>
+    <eg-grid-field label="[% l('Label') %]"           path="label"></eg-grid-field>
+    <eg-grid-field label="[% l('ID') %]" path='id' required hidden></eg-grid-field>
+    <eg-grid-field path='*' hidden></eg-grid-field>
+</eg-grid>
+
+[% END %]
index 36faae3..44013fe 100644 (file)
@@ -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" ]
index 21f300f..78ac249 100644 (file)
@@ -1074,3 +1074,199 @@ INSERT INTO authority.record_entry(marc, last_xact_id) VALUES ($$<marcxml:record
       <marcxml:subfield code="a">Puppies</marcxml:subfield>
     </marcxml:datafield>
          </marcxml:record>$$, :xact_id);
+INSERT INTO authority.record_entry(marc, last_xact_id) VALUES ($$<marcxml:record xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:marcxml="http://www.loc.gov/MARC21/slim" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" xmlns:ri="http://id.loc.gov/ontologies/RecordInfo#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mets="http://www.loc.gov/METS/">
+  <marcxml:leader>04524cz  a2200769n  4500</marcxml:leader>
+  <marcxml:controlfield tag="001">sh85082617</marcxml:controlfield>
+  <marcxml:controlfield tag="003">DLC</marcxml:controlfield>
+  <marcxml:controlfield tag="005">20151028130522.2</marcxml:controlfield>
+  <marcxml:controlfield tag="008">860211|| anannbabn          |a ana      </marcxml:controlfield>
+  <marcxml:datafield tag="010" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">sh 85082617 </marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="034" ind1=" " ind2=" ">
+    <marcxml:subfield code="d">W1510027</marcxml:subfield>
+    <marcxml:subfield code="e">W1510027</marcxml:subfield>
+    <marcxml:subfield code="f">N0630410</marcxml:subfield>
+    <marcxml:subfield code="g">N0630410</marcxml:subfield>
+    <marcxml:subfield code="2">geonames</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="040" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">DLC</marcxml:subfield>
+    <marcxml:subfield code="c">DLC</marcxml:subfield>
+    <marcxml:subfield code="d">DLC</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="151" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Denali, Mount (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Bolshoy Mountain (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Bulshaia Gora (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Bulshaya Gora (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Bulshoe (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Churchill Peaks (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Deenaalee Mountain (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Deenadhee (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Deenadheet (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Deenalee (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Deghilaay Ce'e (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Deghilaay Ke'e (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Delaykah (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Denadhe (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Denagadh (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Denaze (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dengadh (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dengadhe (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dengadhi (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dengadhiy (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Densmore's Mountain (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Densmores Peak (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dghelaay Ce'e (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dghelaay Ke'e (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dghelay Ka'a (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Dghili Ka'a (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Diinaadhi (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Diinaadhii (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Diinaadhiit (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Diinaalii (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Diinaazii (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Diineezi (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Din-al-ee (Alaska : Mount Denali)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Din-az-ee (Alaska : Mount Denali)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Doleika (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Doleyka (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="w">nne</marcxml:subfield>
+    <marcxml:subfield code="a">McKinley, Mount (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Mount Denali (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Mount Doleika (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Mount McKinley (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">North Peak (Alaska : Mount Denali)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">South Peak (Alaska : Mount Denali)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Tenada (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Tenda (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Tennaly (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">To-lah-gah (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Traleika (Alaska : Mount Denali)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="451" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Traleyka (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="551" ind1=" " ind2=" ">
+    <marcxml:subfield code="w">g</marcxml:subfield>
+    <marcxml:subfield code="a">Alaska Range (Alaska)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="550" ind1=" " ind2=" ">
+    <marcxml:subfield code="w">g</marcxml:subfield>
+    <marcxml:subfield code="a">Mountains</marcxml:subfield>
+    <marcxml:subfield code="z">Alaska</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="670" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">GeoNames [algorithmically matched]</marcxml:subfield>
+    <marcxml:subfield code="b">summit; 63°04ʹ10ʺN 151°00ʹ27ʺW</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="670" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">GNIS, Aug. 31, 2015:</marcxml:subfield>
+    <marcxml:subfield code="b">(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)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="670" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Washington Post online, Aug. 31, 2015:</marcxml:subfield>
+    <marcxml:subfield code="b">(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.")</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="670" ind1=" " ind2=" ">
+    <marcxml:subfield code="a">Columbia gazetteer online, Aug. 31, 2015:</marcxml:subfield>
+    <marcxml:subfield code="b">(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.)</marcxml:subfield>
+  </marcxml:datafield>
+  <marcxml:datafield tag="781" ind1=" " ind2="0">
+    <marcxml:subfield code="z">Alaska</marcxml:subfield>
+    <marcxml:subfield code="z">Denali, Mount</marcxml:subfield>
+  </marcxml:datafield>
+</marcxml:record>$$, :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 (file)
index 0000000..1542219
--- /dev/null
@@ -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 = '<eg-edit-fm-record idl-class="ahf" mode="update" record-id="id" on-save="ok" on-cancel="cancel"></eg-edit-fm-record>';
+        } else {
+            templ = '<eg-edit-fm-record idl-class="ahf" mode="create" on-save="ok" on-cancel="cancel"></eg-edit-fm-record>';
+        }
+        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'];
+        }
+    }
+}])