From: <wit...@us...> - 2012-06-28 09:20:21
|
Revision: 16754 http://exist.svn.sourceforge.net/exist/?rev=16754&view=rev Author: withanage Date: 2012-06-28 09:20:08 +0000 (Thu, 28 Jun 2012) Log Message: ----------- [tamboti] customized image theme for tamboti Added Paths: ----------- apps/tamboti/themes/images/ apps/tamboti/themes/images/retrieve-mods.xql apps/tamboti/themes/images/session.xql Added: apps/tamboti/themes/images/retrieve-mods.xql =================================================================== --- apps/tamboti/themes/images/retrieve-mods.xql (rev 0) +++ apps/tamboti/themes/images/retrieve-mods.xql 2012-06-28 09:20:08 UTC (rev 16754) @@ -0,0 +1,2427 @@ +module namespace mods="http://www.loc.gov/mods/v3"; + +declare namespace mads="http://www.loc.gov/mads/v2"; +declare namespace xlink="http://www.w3.org/1999/xlink"; +declare namespace fo="http://www.w3.org/1999/XSL/Format"; +declare namespace functx = "http://www.functx.com"; +declare namespace e = "http://www.asia-europe.uni-heidelberg.de/"; + +import module namespace config="http://exist-db.org/mods/config" at "../config.xqm"; +import module namespace uu="http://exist-db.org/mods/uri-util" at "uri-util.xqm"; + +(: Removes titleIfo, name and relatedItem nodes that do not contain nodes required by the respective elements. :) +declare function mods:remove-parent-with-missing-required-node($node as node()) as node() { +element {node-name($node)} +{ +for $element in $node/* +return + if ($element instance of element(mods:titleInfo) and not($element/mods:title/text())) + then () + else + if ($element instance of element(mods:name) and not($element/mods:namePart/text())) + then () + else + if ($element instance of element(mods:relatedItem)) + then + if (not(((string-length($element) > 0) or ($element/@xlink:href)))) + then () + else $element + else $element +} +}; + +declare option exist:serialize "media-type=text/xml"; + +(: TODO: A lot of restrictions to the first item in a sequence ([1]) have been made; these must all be changed to for-structures or string-joins. :) + +(: ### general functions begin ###:) + +declare function functx:substring-before-last-match($arg as xs:string?, $regex as xs:string) as xs:string? { + replace($arg,concat('^(.*)',$regex,'.*'),'$1') +} ; + + (:~ +: Used to transform the camel-case names of MODS elements into space-separated words. +: @param +: @return +: @see http://www.xqueryfunctions.com/xq/functx_camel-case-to-words.html +:) +declare function functx:camel-case-to-words($arg as xs:string?, $delim as xs:string ) as xs:string? { + concat(substring($arg,1,1), replace(substring($arg,2),'(\p{Lu})', concat($delim, '$1'))) +}; + +(:~ +: Used to capitalize the first character of $arg. +: @param +: @return +: @see http://http://www.xqueryfunctions.com/xq/functx_capitalize-first.html +:) +declare function functx:capitalize-first($arg as xs:string?) as xs:string? { + concat(upper-case(substring($arg,1,1)), + substring($arg,2)) +}; + +(:~ +: Used to remove whitespace at the beginning and end of a string. +: @param +: @return +: @see http://http://www.xqueryfunctions.com/xq/functx_trim.html +:) +declare function functx:trim($arg as xs:string?) as xs:string { + replace(replace($arg,'\s+$',''),'^\s+','') +}; + +(:~ +: Used to clean up unintended sequences of punctuation. These should ideally be removed at the source. +: @param +: @return +:) +(: Function to clean up unintended punctuation. These should ideally be removed at the source. :) +declare function mods:clean-up-punctuation($element as node()) as node() { + element {node-name($element)} + {$element/@*, + for $child in $element/node() + return + if ($child instance of text()) + then + replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace( + ($child) + (:, '\s*\)', ')'):) (:, '\s*;', ';'):) (:, ',,', ','):) (:, '”\.', '.”'):) (:, '\. ,', ','):) (:, ',\s*\.', ''):) (:,'\.\.', '.'):) (:,'\.”,', ',”'):) + , '\s*\.', '.') + , '\s*,', ',') + , ' :', ':') + , ' ”', '”') + , '\.\.', '.') + , '“ ', '“') + , '\?\.', '?') + , '!\.', '!') + ,'\.”\.', '.”') + ,' \)', ')') + ,'\( ', '(') + else mods:clean-up-punctuation($child) + } +}; + + +(: ### general functions end ###:) + + +(:~ +: The <b>mods:get-language-term</b> function returns +: the <b>human-readable label</b> of the language value passed to it. +: This value can set in many mods elements and attributes. +: languageTerm can have two types, text and code. +: Type code can use two different authorities, +: recorded in the code tables language-2-type-codes.xml and language-3-type-codes.xml, +: as well as the authority valueTerm noted in language-3-type-codes.xml. +: The most commonly used values are checked first, letting the function exit quickly. +: The function returns the human-readable label, based on searches in the code values and in the label. +: +: @param $node A mods element or attribute recording a value, in textual or coded form +: @return The language label string +:) +declare function mods:get-language-label($language as item()*) as xs:string* { + let $languageTerm := + let $languageTerm := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[value = $language]/label + return + if ($languageTerm) + then $languageTerm + else + let $languageTerm := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[valueTwo = $language]/label + return + if ($languageTerm) + then $languageTerm + else + let $languageTerm := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[valueTerm = $language]/label + return + if ($languageTerm) + then $languageTerm + else + let $languageTerm := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[upper-case(label) = $language/upper-case(label)]/label + return + if ($languageTerm) + then $languageTerm + else + let $languageTerm := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[upper-case(label) = upper-case($language)]/label + return + if ($languageTerm) + then $languageTerm + else $language + return $languageTerm +}; + +(:~ +: The <b>mods:get-script-term</b> function returns +: the <b>human-readable label</b> of the script value passed to it. +: @param +: @return +:) +declare function mods:get-script-term($language as node()*) as xs:string* { + let $scriptTerm := + let $scriptTerm := doc(concat($config:edit-app-root, '/code-tables/script-codes.xml'))/code-table/items/item[value = $language/mods:scriptTerm[@authority]]/label + return + if ($scriptTerm) + then $scriptTerm + else + let $scriptTerm := doc(concat($config:edit-app-root, '/code-tables/script-codes.xml'))/code-table/items/item[value = $language/mods:scriptTerm]/label + return + if ($scriptTerm) + then $scriptTerm + else () + return $scriptTerm +}; + +(:~ +: The <b>mods:language-of-resource</b> function returns +: the <b>string</b> value of the language for the resource. +: This value is set in mods/language/languageTerm. +: The function feeds this value to the function mods:get-language. +: It is assumed that if two languageTerm's exist under one language, these are equivalent. +: It is possible to have multiple mods/language for resources, just as it is possible to set the code value to 'mul', meaning Multiple languages. +: The value is set in the dialogue which leads to the creation of a new records. +: +: @see xqdoc/xqdoc-display;get-language +: @param $language The MODS languageTerm element, child of the top-level language element +: @return The language label string +:) +declare function mods:language-of-resource($language as element()*) as xs:anyAtomicType* { + let $languageTerm := $language/mods:languageTerm[1] + return + if ($languageTerm) + then mods:get-language-label($languageTerm) + else () +}; + +declare function mods:script-of-resource($language as element()*) as xs:anyAtomicType* { + let $scriptTerm := $language/mods:scriptTerm + return + if ($scriptTerm) + then mods:get-script-term($language) + else () +}; + + +(:~ +: The <b>mods:language-of-cataloging</b> function returns +: the <b>$string</b> value of the language for cataloguing the resource. +: This value is set in mods/recordInfo/languageOfCataloging. +: The function feeds this value to the function mods:get-language. +: It is assumed that if two languageTerm's exist under one language, these are equivalent. +: It is possible to have multiple mods/language, for resources, just as it is possible to set the code value to 'mul', meaning Multiple languages. +: The value is set in the dialogue which leads to the creation of a new records. +: +: @see xqdoc/xqdoc-display;get-language +: @param $entry The MODS languageOfCataloging element, child of the top-level recordInfo element +: @return The language label string +:) +declare function mods:language-of-cataloging($language as element(mods:languageOfCataloging)*) as xs:anyAtomicType? { + let $languageTerm := $language/mods:languageTerm[1] + return + if ($languageTerm) + then mods:get-language-label($languageTerm) + else () +}; + +(:~ +: The <em>mods:get-role-label-for-detail-view</em> function returns +: the <em>human-readable value</em> of the roleTerm passed to it. +: Whereas mods:get-role-label-for-detail-view returns the author/creator roles that are placed in front of the title in detail view, +: mods:get-role-label-for-detail-view returns the secondary roles that are placed after the title in list view and in relatedItem in detail view. +: The value occurs in mods/name/role/roleTerm. +: It can have two types, text and code. +: Type code can use the marcrelator authority, recorded in the code table role-codes.xml. +: The most commonly used values are checked first, letting the function exit quickly. +: The function returns the human-readable label, based on searches in the code values and in the label values. +: +: @param $node A mods element or attribute recording a role term value, in textual or coded form +: @return The role term label string +:) +declare function mods:get-role-label-for-detail-view($roleTerm as item()?) as item()? { + let $roleLabel := + (: Is the roleTerm a role label? :) + let $roleLabel := doc(concat($config:edit-app-root, '/code-tables/role-codes.xml'))/code-table/items/item[upper-case(label) eq upper-case($roleTerm)]/label + (: Prefer the label proper, since it contains the form presented in the detail view, e.g. "Editor" instead of "edited by". :) + return + if ($roleLabel) + then $roleLabel + else + (: Is the roleTerm a role term @code? :) + let $roleLabel := doc(concat($config:edit-app-root, '/code-tables/role-codes.xml'))/code-table/items/item[value eq $roleTerm]/label + return + if ($roleLabel) + then $roleLabel + else $roleTerm + return functx:capitalize-first($roleLabel) +}; + +declare function mods:get-roles-for-detail-view($name as element()*) as item()* { + if ($name/mods:role/mods:roleTerm/text()) + then + let $roles := $name/mods:role + for $role at $pos in $name/mods:role + return + distinct-values( + if ($pos eq 1) + then mods:get-role-terms-for-detail-view($role) + else (' and ', mods:get-role-terms-for-detail-view($role)) + ) + else + (: Default values in the absence of $roleTerm. :) + if ($name/@type eq 'corporate') + then 'Corporation' + else 'Author' +}; + +declare function mods:get-role-terms-for-detail-view($role as element()*) as item()* { + let $roleTerms := $role/mods:roleTerm + for $roleTerm at $pos in distinct-values($roleTerms) + + return + if ($roleTerm) + then mods:get-role-label-for-detail-view($roleTerm) + else () + +}; + +(:~ +: The <em>mods:get-role-label-for-list-view</em> function returns +: the <em>human-readable value</em> of the roleTerm passed to it. +: Whereas mods:get-role-label-for-detail-view returns the author/creator roles that are placed in front of the title in detail view, +: mods:get-role-label-for-detail-view returns the secondary roles that are placed after the title in list view and in relatedItem in detail view.: The value occurs in mods/name/role/roleTerm. +: It can have two types, text and code. +: Type code can use the marcrelator authority, recorded in the code table role-codes.xml. +: The most commonly used values are checked first, letting the function exit quickly. +: The function returns the human-readable label, based on searches in the code values and in the labelSecondary and label values. +: +: @param $node A mods element or attribute recording a role term value, in textual or coded form +: @return The role term label string +:) +declare function mods:get-role-label-for-list-view($roleTerm as xs:string*) as xs:string* { + let $roleLabel := + let $roleLabel := doc(concat($config:edit-app-root, '/code-tables/role-codes.xml'))/code-table/items/item[upper-case(label) eq upper-case($roleTerm)]/labelSecondary + (: Prefer labelSecondary, since it contains the form presented in the list view output, e.g. "edited by" instead of "editor". :) + return + if ($roleLabel) + then $roleLabel + else + let $roleLabel := doc(concat($config:edit-app-root, '/code-tables/role-codes.xml'))/code-table/items/item[value eq $roleTerm]/labelSecondary + return + if ($roleLabel) + then $roleLabel + else + let $roleLabel := doc(concat($config:edit-app-root, '/code-tables/role-codes.xml'))/code-table/items/item[upper-case(label) eq upper-case($roleTerm)]/label + (: If there is no labelSecondary, take the label. :) + return + if ($roleLabel) + then $roleLabel + else + let $roleLabel := doc(concat($config:edit-app-root, '/code-tables/role-codes.xml'))/code-table/items/item[value eq $roleTerm]/label + return + if ($roleLabel) + then $roleLabel + else $roleTerm + (: Do not present default values in case of absence of $roleTerm, since primary roles are not displayed in list view. :) + return ($roleLabel, ' ') +}; + +declare function mods:add-part($part, $sep as xs:string) { + if (empty($part) or string-length($part[1]) eq 0) + then () + else concat(string-join($part, ' '), $sep) +}; + +declare function mods:get-publisher($publishers as element(mods:publisher)*) as item()* { + string-join( + for $publisher in $publishers + return + (: NB: Using name here is an expansion of the MODS schema.:) + if ($publisher/mods:name) + then mods:retrieve-name($publisher/mods:name, 1, 'secondary', '') + else $publisher + , + (: If there is a transliterated publisher, probably only one publisher is referred to. :) + if ($publishers[@transliteration] or $publishers[mods:name/@transliteration]) + then ' ' + else + ' and ') +}; + + + +declare function mods:generate-swd-url($label as xs:string, $url as xs:string) +{ +<a href="http://d-nb.info/gnd/ +{ +$url +}" target="_blank"> +{let $x := $label +return $x +} +</a> +}; + + +(: ### <subject> begins ### :) + +(: format subject :) +declare function mods:format-subjects($entry as element(), $global-transliteration) { + for $subject in ($entry/mods:subject) + let $authority := + if ($subject/@authority/string()='local') + then concat('(', ($subject/@authority/string()), ')') + else if ($subject/@authority/string()='swd') + then concat('(', ($subject/@authority/string()), '*)') + else () + order by fn:lower-case($subject) ascending + return + <tr xmlns="http://www.w3.org/1999/xhtml"> + <td class="label subject">Subject {$authority}</td> + <td class="record"><table class="subject"> + { + for $item in ($subject/mods:*) + let $authority := $item/@authority/string() + let $encoding := + if ($item/@encoding/string()) + then concat('(', ($item/@encoding/string()), ')') + else () + let $type := + if ($item/@type/string()) + then concat('(', ($item/@type/string()), ')') + else () + + order by fn:lower-case($item) ascending + return + <tr><td class="sublabel"> + { + replace(functx:capitalize-first(functx:capitalize-first(functx:camel-case-to-words(replace($item/name(), 'mods:',''), ' '))),'Info',''), + $authority, $encoding, $type + } + </td><td class="subrecord"> + { + (: If there is a child. :) + if ($item/mods:*) + then + (: If it is a name. :) + if ($item/name() eq 'name') + then mods:format-name($item, 1, 'primary', $global-transliteration) + else + (: If it is a titleInfo. :) + if ($item/name() eq 'titleInfo') + (: NB: What if there is more than one titleInfo? Here one steps out of the iteration. :) + then string-join(mods:get-short-title($item/..), '') + else + (: If it is something else, such as topic (caught by $subitem/name()). :) + for $subitem in ($item/mods:*) + let $authority := + if ($subitem/@authority/string()) + then concat('(', ($subitem/@authority/string()), ')') + else () + let $encoding := + if ($subitem/@encoding/string()) + then concat('(', ($subitem/@encoding/string()), ')') + else () + let $type := + if ($subitem/@type/string()) + then concat('(', ($subitem/@type/string()), ')') + else () + return + <table><tr><td class="sublabel"> + {functx:capitalize-first(functx:camel-case-to-words(replace($subitem/name(), 'mods:',''), ' ')), + $authority, $encoding} + </td><td><td class="subrecord"> + {$subitem/string()} + </td></td></tr></table> + else + <table><tr><td class="subrecord" colspan="2">{ + + (:if it is a swd subject, identified by ####:) + let $swd := + if (fn:exists(fn:tokenize( $item/string(),'#+')[2])) + then (mods:generate-swd-url(fn:tokenize($item/string(),'#+')[2],fn:tokenize($item/string(),'#+')[1]) ) + else ($item/string()) + return $swd + }</td></tr></table> + } + </td></tr> + } + </table></td> + </tr> +}; + +(: ### <subject> ends ### :) + +(: ### <extent> begins ### :) + +(: <extent> belongs to <physicalDescription>, to <part> as a top level element and to <part> under <relatedItem>. +Under <physicalDescription>, <extent> has no subelements.:) + + + +declare function mods:get-extent($extent as element(mods:extent)?) as xs:string? { +let $unit := $extent/@unit +let $start := $extent/mods:start +let $end := $extent/mods:end +let $total := $extent/mods:total +let $list := $extent/mods:list +return + if ($start and $end) + then + (: Chicago does not note units :) + (: + concat( + if ($unit) + then concat($unit, ' ') + else () + , + :) + if ($start ne $end) + then concat($start, '-', $end) + else $start + else + if ($start or $end) + then + if ($start) + then $start + else $end + else + if ($total) + then concat($total, ' ', $unit) + else + if ($list) + then $list + else string-join($extent/string(), ' ') +}; + +declare function mods:get-date($date as element()*) as xs:string* { + (: contains no subelements. :) + (: has: encoding; point; qualifier. :) + (: NB: some dates have keyDate. :) + +let $start := $date[@point eq 'start']/text() +let $end := $date[@point eq 'end']/text() +let $qualifier := $date/@qualifier/text() + +let $encoding := $date/@encoding +return + ( + if ($start and $end) + then + if ($start ne $end) + then concat($start, '-', $end) + else $start + else + if ($start or $end) + then + if ($start) + then concat($start, '-?') + else concat('?-', $end) + (: if neither $start nor $end. :) + else $date + , + if ($qualifier) + then ('(', $qualifier, ')') + else () + ) +}; + +(: ### <originInfo> begins ### :) + +(: The DLF/Aquifer Implementation Guidelines for Shareable MODS Records require the use of at least one <originInfo> element with at least one date subelement in every record, one of which must be marked as a key date. <place>, <publisher>, and <edition> are recommended if applicable. These guidelines make no recommendation on the use of the elements <issuance> and <frequency>. This element is repeatable. :) + (: Application: :) + (: Problem: :) +(: Attributes: lang, xml:lang, script, transliteration. :) + (: Unaccounted for: :) +(: Subelements: <place> [RECOMMENDED IF APPLICABLE], <publisher> [RECOMMENDED IF APPLICABLE], <dateIssued> [AT LEAST ONE DATE ELEMENT IS REQUIRED], <dateCreated> [AT LEAST ONE DATE ELEMENT IS REQUIRED], <dateCaptured> [NOT RECOMMENDED], <dateValid> [NOT RECOMMENDED], <dateModified> [NOT RECOMMENDED], <copyrightDate> [AT LEAST ONE DATE ELEMENT IS REQUIRED], <dateOther> [AT LEAST ONE DATE ELEMENT IS REQUIRED], <edition> [RECOMMENDED IF APPLICABLE], <issuance> [OPTIONAL], <frequency> [OPTIONAL]. :) + (: Unaccounted for: . :) + (: <place> :) + (: Repeat <place> for recording multiple places. :) + (: Attributes: type [RECOMMENDED IF APPLICABLE] authority [RECOMMENDED IF APPLICABLE]. :) + (: @type :) + (: Values: :) + (: Unaccounted for: :) + (: Subelements: <placeTerm> [REQUIRED]. :) + (: Attributes: type [REQUIRED]. :) + (: Values: text, code. :) + (: <publisher> :) + (: Attributes: none. :) + (: dates [AT LEAST ONE DATE ELEMENT IS REQUIRED] :) + (: The MODS schema includes several date elements intended to record different events that may be important in the life of a resource. :) + +declare function mods:get-place($places as element(mods:place)*) as xs:string? { + string-join( + for $place in $places + let $placeTerms := $place/mods:placeTerm + return + string-join( + for $placeTerm in $placeTerms + let $order := + if ($placeTerm/@transliteration) + then 0 + else 1 + order by $order + return + if ($placeTerm[@type eq 'text']/text()) + then concat + ( + $placeTerm[@transliteration]/text() + , + ' ' + , + $placeTerm[not(@transliteration)]/text() + ) + else + if ($placeTerm[@authority eq 'marccountry']/text()) + then doc(concat($config:edit-app-root, '/code-tables/marc-country-codes.xml'))/code-table/items/item[value eq $placeTerm]/label + else + if ($placeTerm[@authority eq 'iso3166']/text()) + then doc(concat($config:edit-app-root, '/code-tables/iso3166-country-codes.xml'))/code-table/items/item[value eq $placeTerm]/label + else $place/mods:placeTerm[not(@type)]/text(), + ' ') + , + (: If there is a transliterated place term, probably only one place is referred to. :) + if ($places[@transliteration] or $places[mods:placeTerm/@transliteration]) + then ' ' + else + ' and ') +}; + +(: NB: This function should be split up in a part and an originInfo function.:) +(: <part> is found both as a top level element and under <relatedItem>. $entry can be both mods and relatedItem. :) +(: NB: where is the relatedItem type? :) +(: Used in list view and display of related items in list and detail view. :) +declare function mods:get-part-and-origin($entry as element()) { + let $originInfo := $entry/mods:originInfo[1] + (: contains: place, publisher, dateIssued, dateCreated, dateCaptured, dateValid, + dateModified, copyrightDate, dateOther, edition, issuance, frequency. :) + (: has: lang; xml:lang; script; transliteration. :) + let $place := $originInfo/mods:place + (: contains: placeTerm. :) + (: has no attributes. :) + (: handled by get-place(). :) + + let $publisher := $originInfo/mods:publisher + (: contains no subelements. :) + (: has no attributes. :) + (: handled by get-publisher(). :) + + let $dateIssued := $originInfo/mods:dateIssued[1] + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + let $dateCreated := $originInfo/mods:dateCreated + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + let $dateCaptured := $originInfo/mods:dateCaptured + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + let $dateValid := $originInfo/mods:dateValid + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + let $dateModified := $originInfo/mods:dateModified + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + let $copyrightDate := $originInfo/mods:copyrightDate + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + let $dateOther := $originInfo/mods:dateOther + (: contains no subelements. :) + (: has: encoding; point; keyDate; qualifier. :) + (: pick the "strongest" value for the hitlist. :) + let $dateOriginInfo := + if ($dateIssued) + then $dateIssued + else + if ($copyrightDate) + then $copyrightDate + else + if ($dateCreated) + then $dateCreated + else + if ($dateCaptured) + then $dateCaptured + else + if ($dateModified) + then $dateModified + else + if ($dateValid) + then $dateValid + else + if ($dateOther) + then $dateOther + else () + let $dateOriginInfo := mods:get-date($dateOriginInfo) + + (: NB: this should iterate over part, since there are e.g. multi-part installments of articles. :) + let $part := $entry/mods:part[1] + (: contains: detail, extent, date, text. :) + (: has: type, order, ID. :) + let $detail := $part/mods:detail + (: contains: number, caption, title. :) + (: has: type, level. :) + let $issue := $detail[@type=('issue', 'number')]/mods:number[1]/text() + let $volume := + if ($detail[@type='volume']/mods:number/text()) + then $detail[@type='volume']/mods:number/text() + (: NB: to accommodate erroneous Zotero export. Only number is valid. :) + else $detail[@type='volume']/mods:text/text() + (: NB: Does $page exist? :) + let $page := $detail[@type='page']/mods:number/text() + (: $page resembles list. :) + + let $extent := $part/mods:extent + (: contains: start, end, total, list. :) + (: has: unit. :) + (: handled by mods:get-extent(). :) + + (: NB: If the date of a periodical issue is wrongly put in originInfo/dateIssued. Delete when MODS export is corrected.:) + let $datePart := + if ($part/mods:date) + then mods:get-date($part/mods:date) + else $dateOriginInfo + (: contains no subelements. :) + (: has: encoding; point; qualifier. :) + + let $text := $part/mods:text + (: contains no subelements. :) + (: has no attributes. :) + + return + (: If there is a part with issue information and a date, i.e. if the publication is an article in a periodical. :) + (: NB: "not($place or $publisher" is a little risky since full entries of periodicals have these elements. :) + if ($datePart and ($volume or $issue or $extent or $page) and not($place or $publisher)) + then + concat( + ' ' + , + if ($volume and $issue) + then concat($volume, ', no. ', $issue + , + concat(' (', $datePart, ')') + ) + (: concat((if ($part/mods:detail/mods:caption) then $part/mods:detail/mods:caption/string() else '/'), $part/mods:detail[@type='issue']/mods:number) :) + else + if ($issue or $volume) + then + (: If the year is used as volume. :) + if ($issue) + then concat( + concat(' ', $datePart) + , ', no. ', $issue) + else concat($volume, concat(' (', string-join($datePart, ', '), ')')) + else + if ($extent and $datePart) + (: We have date and extent alone. :) + then concat(' ', $datePart) + else () + , + (: NB: We assume that there will not both be $page and $extent.:) + if ($extent) + then concat(': ', mods:get-extent($extent[1]), '.') + else + if ($page) + then concat(': ', $page[1], '.') + else '.' + ) + else + (: If there is a dateIssued (loaded in $datePart) and a place or a publisher, i.e. if the publication is an an edited volume. :) + if ($datePart and ($place or $publisher)) + then + ( + if ($volume) + then concat(', Vol. ', $volume) + else () + , + if ($extent or $page) + then + if ($volume and $extent) + then concat(': ', mods:get-extent($extent)) + else + if ($volume and $page) + then concat(': ', $page) + else + if ($extent) + then concat(', ', mods:get-extent($extent)) + else + if ($page) + then concat(': ', $page) + else () + else + if ($volume) + then ', ' + else () + , + if ($place) + then concat('. ', mods:get-place($place)) + else () + , + if ($place and $publisher) + then (': ', mods:get-publisher($publisher)) + else () + , + if ($datePart) + then + (', ', + for $date in $datePart + return + string-join($date, ' and ') + ) + else () + , + '.' + ) + (: If not a periodical and not an edited volume, we don't really know what it is and just try to extract whatever information there is. :) + else + ( + if ($place) + then mods:get-place($place) + else () + , + if ($publisher) + then ( + if ($place) + then ': ' + else () + , normalize-space(mods:add-part(mods:get-publisher($publisher), ', ')) + ) + else () + , + mods:add-part($dateOriginInfo + , + if (exists($entry/mods:relatedItem[@type='host']/mods:part/mods:extent) or exists($entry/mods:relatedItem[@type='host']/mods:part/mods:detail)) + then '.' + else () + ) + , + if (exists($extent/mods:start) or exists($extent/mods:end) or exists($extent/mods:list)) + then (': ', mods:get-extent($extent)) + else () + , + (: If it is a series:) + (: NB: elaborate! :) + if ($volume) + then concat(', Vol. ', $volume, '.') + else () + , + if ($text) + then concat(' ', $text) + else () + ) +}; + + +(: ### <originInfo> ends ### :) + +(: ### <name> begins ### :) + +(: The DLF/Aquifer Implementation Guidelines for Shareable MODS Records requires the use of at least one <name> element to describe the creator of the intellectual content of the resource, if available. The guidelines recommend the use of the type attribute with all <name> elements whenever possible for greater control and interoperability. In addition, they require the use of <namePart> as a subelement of <name>. This element is repeatable. :) + (: Application: :) + (: Problem: :) +(: Attributes: type [RECOMMENDED], authority [RECOMMENDED], xlink, ID, lang, xml:lang, script, transliteration. :) + (: Unaccounted for: authority, xlink, ID, (lang), xml:lang, script. :) + (: @type :) + (: Values: personal, corporate, conference. :) + (: Unaccounted for: none. :) +(: Subelements: <namePart> [REQUIRED], <displayForm> [OPTIONAL], <affiliation> [OPTIONAL], <role> [RECOMMENDED], <description> [NOT RECOMMENDED]. :) + (: Unaccounted for: <displayForm>, <affiliation>, <role>, <description>. :) + (: <namePart> :) + (: "namePart" includes each part of the name that is parsed. Parsing is used to indicate a date associated with the name, to parse the parts of a corporate name (MARC 21 fields X10 subfields $a and $b), or to parse parts of a personal name if desired (into family and given name). The latter is not done in MARC 21. Names are expected to be in a structured form (e.g. surname, forename). :) + (: Attributes: type [RECOMMENDED IF APPLICABLE]. :) + (: @type :) + (: Values: date, family, given, termsOfAddress. :) + (: Unaccounted for: date, termsOfAddress :) + (: Subelements: none. :) + (: <role> :) + (: Attributes: none. :) + (: Subelements: <roleTerm> [REQUIRED]. :) + (: <roleTerm> :) + (: Unaccounted for: none. :) + (: Attributes: type [RECOMMENDED], authority [RECOMMENDED IF APPLICABLE]. :) + (: Unaccounted for: type [RECOMMENDED], authority [RECOMMENDED IF APPLICABLE] :) + (: @type :) + (: Values: text, code. :) + (: Unaccounted for: text, code :) + +(: Both the name as given in the publication and the autority name should be rendered. :) + +declare function mods:get-conference-hitlist($entry as element(mods:mods)) { + let $date := ($entry/mods:originInfo[1]/mods:dateIssued/string()[1], $entry/mods:part/mods:date/string()[1], + $entry/mods:originInfo[1]/mods:dateCreated/string())[1] + let $conference := $entry/mods:name[@type eq 'conference']/mods:namePart + return + if ($conference) + then + concat('Paper presented at ', + mods:add-part($conference/string(), ', '), + mods:add-part($entry/mods:originInfo[1]/mods:place/mods:placeTerm, ', '), + $date + ) + else () +}; + +declare function mods:get-conference-detail-view($entry as element()) { + (: let $date := ($entry/mods:originInfo/mods:dateIssued/string()[1], $entry/mods:part/mods:date/string()[1], + $entry/mods:originInfo/mods:dateCreated/string())[1] + return :) + let $conference := $entry/mods:name[@type eq 'conference']/mods:namePart + return + if ($conference) + then + concat('Paper presented at ', $conference/string() + (: , mods:add-part($entry/mods:originInfo/mods:place/mods:placeTerm, ', '), $date:) + (: no need to duplicate placeinfo in detail view. :) + ) + else () +}; + +declare function mods:format-name($name as element()?, $pos as xs:integer, $caller as xs:string, $global-transliteration as xs:string) { + (: $nameLanguageLabel is retrieved only in order to get the name order. :) + let $nameLanguageLabel := + if ($name/@lang) + then mods:get-language-label($name/@lang) + else mods:language-of-resource($name/../mods:language) + let $nameOrder := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[label eq $nameLanguageLabel]/nameOrder/string() + let $nameStyle := + if ($nameLanguageLabel = ('Chinese','Japanese','Korean','Vietnamese')) + then 'EastAsian' + else 'not' + let $nameType := $name/@type + + return + (: If the name is (erroneously) not typed (as personal corporate, conference, or family), then string-join the transliterated name parts and string-join the untransliterated nameParts. :) + (: NB: One could also decide to treat it as a personal name. :) + if (not($nameType)) + then + concat( + (: The namespace is masked because it refers to both the mods and the mads prefix.:) + string-join($name/*:namePart[exists(@transliteration)], ' ') + , ' ', + string-join($name/*:namePart[not(@transliteration)], ' ') + ) + (: If the name is typed :) + else + (: If the name is type conference. :) + if ($nameType eq 'conference') + then () + (: Do nothing, since get-conference-detail-view and get-conference-hitlist take care of conference. :) + else + (: If the name is type corporate. :) + if ($nameType eq 'corporate') + then + concat( + string-join( + for $item in $name/*:namePart + where exists($item/@transliteration) + return $item + , ', ') + + , ' ', + string-join( + for $item in $name/*:namePart + where not($item/@transliteration) + return $item + , ', ') + ) + (: The assumption is that any sequence of corporate name parts is meaningfully constructed, e.g. with more general term first. :) + (: NB: this is the same as no type. :) + (: NB: Make conditional for remaining MODS 3.4. type value: "family". :) + (: If the name is type personal. This is the last option. :) + else + (: Split up the name parts into three groups: + 1. Base: those that do not have a transliteration attribute and that do not have a script attribute (or have Latin script). + 2. Transliteration: those that have transliteration and do not have script (or have Latin script, which all transliterations have implicitly). + 3. Script: those that do not have transliteration, but have script (but not Latin script, which characterises transliterations). :) + (: NB: The assumption is that transliteration is always in Latin script, but - obviously - it may e.g. be in Cyrillic script. :) + (: If the above three name groups occur, they should be formatted in the sequence of 1, 2, and 3. + Only in rare cases will 1, 2, and 3 occur together (e.g. a Westerner with name form in Chinese characters or a Chinese with an established Western-style name form different from the transliterated name form. + In the case of persons using Latin script to render their name, only 1 will be used. Here we have the typical Western names. + In the case of e.g. Chinese or Russian names, only 2 and 3 will be used. + Only 3 will be used if no transliteration is given. + Only 2 will be used if only transliteration is given. :) + (: When formatting a name, $pos is relevant to the formatting of Base, i.e. to Western names, and to Russian names in Script and Transliteration. + Hungarian is special, in that it uses Latin script, but has the name order family-given. :) + (: When formatting a name, the first question to ask is whether the name parts are typed, i.e. are divded into given and family name parts (plus date and terms of address). + If they are not, there is really not much one can do, besides concatenating the name parts and trusting that their sequence is meaningful. :) + (: NB: If the name is translated from one language to another (e.g. William the Conqueror, Guillaume le Conquérant), there will be two $nameBasic, one for each language. This is not handled. :) + (: NB: If the name is transliterated in two ways, there will be two $nameTransliteration, one for each transliteration scheme. This is not handled. :) + (: NB: If the name is rendered in two scripts, there will be two $nameScript, one for each script. This is not handled. :) + let $nameContainsTransliteration := + if ($name[*:namePart[@transliteration]]) + then 1 + else + if ($global-transliteration) + then 1 + else 0 + (: If the name does not contain a namePart with transliteration, it is a basic name, i.e. a name where the distinction between the name in native script and in transliteration does not arise. + Typical examples are Western names, but also include Eastern names where no effort has been taken to distinguish between native script and transliteration. + Filtering like this would leave out names where Westerners have Chinese names, and in order to catch these, we require that they have language set to English. :) + let $nameBasic := + if (not($nameContainsTransliteration)) + then <name>{$name/*:namePart[not(@transliteration) and (not(@script) or @script = ('Latn', 'latn', 'Latin'))]}</name> + else <name>{$name/*:namePart[@lang eq 'eng']}</name> + + (: If there is transliteration, there are nameParts with transliteration. + To filter these, we seek nameParts + which contain the transliteration attribute, even though this may be empty (this is special to the templates, since they allow the user to set a global transliteration value, to be applied whereever an empty transliteration attribute occurs; and + which do not contain the script attribute or which have the script attribute set to Latin (defining of transliterations here). :) + (: NB: Should English names be filtered away?:) + let $nameTransliteration := + if ($nameContainsTransliteration) + then <name>{$name/*:namePart[@transliteration and (not(@script) or (@script = ('Latn', 'latn', 'Latin')))]}</name> + else () + + (: If there is transliteration, the presumption must be that all nameParts which are not transliterations (and which do not have the language set to English) are names in non-Latin script. We filter for nameParts + which do no have the transliteration attribute or have one with no contents, and + which do not have script set to Latin, and + which do not have English as their language. :) + let $nameScript := + if ($nameContainsTransliteration) + then <name>{$name/*:namePart[(not(@transliteration) or string-length(@transliteration) eq 0) and not(@script = ('Latn', 'latn', 'Latin')) and not(@lang='eng')]}</name> + else () + (: We assume that there is only one date name part. The date name parts with transliteration and script are rather theoretical. This date is attached at the end of the name. :) + let $dateBase := $name/*:namePart[@type eq 'date'][1] + + (: We try only the most obvious place for a lang and script attribute on namePart. :) + (: NB: Only the first value is chosen, so names cannot have several language. :) + let $namePartFamilyLanguage := $name/*:namePart[type eq 'family'][1]/@lang + let $namePartLanguage := + if ($namePartFamilyLanguage) + then mods:get-language-label($namePartFamilyLanguage) + else () + let $namePartLanguageLabel := + (: If there is language on namePart, use that; otherwise use language on name. :) + if ($namePartLanguage) + then $namePartLanguage + else $nameLanguageLabel + (: If there is lang on namePart, use that for retrieving the name order; otherwise use language on name (or, if this did not exist when it was set, the language of the resource as a whole. :) + let $nameOrder := doc(concat($config:edit-app-root, '/code-tables/language-3-type-codes.xml'))/code-table/items/item[label eq $namePartLanguageLabel]/nameOrder/string() + + return + concat( + (: ## 1 ##:) + if ($nameBasic/string()) + (: If there are one or more name parts that are not marked as being transliteration and that are not marked as having a certain script (aside from Latin). :) + then + (: Filter the name parts according to type. :) + let $untyped := <name>{$nameBasic/*:namePart[not(@type)]}</name> + let $family := <name>{$nameBasic/*:namePart[@type eq 'family']}</name> + let $given := <name>{$nameBasic/*:namePart[@type eq 'given']}</name> + let $termsOfAddress := <name>{$nameBasic/*:namePart[@type eq 'termsOfAddress']}</name> + (: let $date := <name>{$nameBasic/*:namePart[@type eq 'date']}</name> :) + + return + if ($untyped/string()) + (: If there are name parts that are not typed, there is nothing we can do to order their sequence. When name parts are not typed, it is generally because the whole name occurs in one name part, formatted for display (usually with a comma between family and given name), but a name part may also be untyped when (non-Western) names that cannot (easily) be divided into family and given names are in evidence. We trust that any sequence of nameparts are meaningfully ordered and simply string-join them. :) + then string-join($untyped/*:namePart, ' ') + else + (: If the name parts are typed, we have here a name divided into given and family name (and so on), a name that is not a transliteration and that is not in a non-Latin script: an ordinary (Western) name. :) + if ($pos eq 1 and $caller eq 'primary') + (: If the name occurs first in primary position (i.e. first in author position in list view) and the name is not a name that occurs in family-given sequence (is not an Oriental or Hungarian name), then format it with a comma between the family name and the given name, with the family name placed first, and append the term of address. :) + (: Dates are appended last, once for the whole name. :) + (: Example: "Freud, Sigmund, Dr. (1856-1939)". :) + then + concat( + (: There may be several instances of the same type of name part; these are joined with a space in between. :) + string-join($family/*:namePart, ' ') + , + if ($family/string() and $given/string()) + (: If only one of family and given are evidenced, no comma is needed. :) + then + if ($nameOrder eq 'family-given') + (: If the name is Hungarian, use a space; otherwise (i.e. in most cases) use a comma. :) + then ' ' + else ', ' + else () + , + string-join($given/*:namePart, ' ') + , + if ($termsOfAddress/string()) + (: If there are several terms of address, join them with a comma in between ("Dr., Prof."). :) + then concat(', ', string-join($termsOfAddress/*:namePart, ', ')) + else () + (: + , + if ($date/string() and $family/string() and $given/string()) + then concat(' (', string-join($date/*:namePart, ', '),')') + else () + :) + ) + else + if ($nameOrder eq 'family-given') + (: If the name is Hungarian and does not occur in primary position. :) + then + concat( + string-join($family/*:namePart, ' ') + , + if ($family/string() and $given/string()) + then ' ' + else () + , + string-join($given/*:namePart, ' ') + , + if ($termsOfAddress/string()) + (: NB: Where do terms of address go in Hungarian? :) + then concat(', ', string-join($termsOfAddress/*:namePart, ', ')) + else () + (: + , + if ($date/string()) + then concat(' (', string-join($date/*:namePart, ', '),')') + else () + :) + ) + else + (: In all other situations, the name order is given-family, with a space in between. :) + (: Example: "Dr. Sigmund Freud (1856-1939)". :) + concat( + if ($termsOfAddress/text()) + then concat(string-join($termsOfAddress/*:namePart, ', '), ' ') + else () + , + string-join($given/*:namePart, ' ') + , + if ($family/string() and $given/string()) + then ' ' + else () + , + string-join($family/*:namePart, ' ') + (: + , + if ($date/text()) + then concat(' (', string-join($date/*:namePart, ', '), ')') + else () + :) + ) + else () + , + ' ' + , + (: If there is an "English" name, enclose the transliterated and Eastern script name in parenthesis. :) + if ($name/*:namePart[@lang eq 'eng']) + then ' (' + else () + , + (: ## 2 ##:) + if ($nameTransliteration/string()) + (: We have a name in transliteration. This can e.g. be a Chinese name or a Russian name. :) + then + let $untypedTransliteration := <name>{$nameTransliteration/*:namePart[not(@type)]}</name> + let $familyTransliteration := <name>{$nameTransliteration/*:namePart[@type eq 'family']}</name> + let $givenTransliteration := <name>{$nameTransliteration/*:namePart[@type eq 'given']}</name> + let $termsOfAddressTransliteration := <name>{$nameTransliteration/*:namePart[@type eq 'termsOfAddress']}</name> + (: let $dateTransliteration := <name>{$nameTransliteration/*:namePart[@type eq 'date']}</name> :) + + return + if ($untypedTransliteration/string()) + then string-join($untypedTransliteration/*:namePart, ' ') + else + (: The name parts are typed, so we have a name that is a transliteration and that is divided into given and family name. If the name order is family-given, we have an ordinary Oriental name in transliteration, if the name order is givenfamily, we have e.g. a Russian name in transliteration. :) + if ($pos eq 1 and $caller eq 'primary' and $nameOrder ne 'family-given') + (: If the name occurs first in primary position (i.e. first in list view) and the name is not a name that occurs in family-given sequence, e.g. a Russian name, format it with a comma between family name and given name, with family name placed first. :) + then + concat( + string-join($familyTransliteration/*:namePart, ' ') + , + if ($familyTransliteration/string() and $givenTransliteration/string()) + then ', ' + else () + , + string-join($givenTransliteration/*:namePart, ' ') + , + if ($termsOfAddressTransliteration/string()) + then concat(', ', string-join($termsOfAddressTransliteration/*:namePart, ', ')) + else () + (: + , + if ($dateTransliteration/string()) + then concat(' (', string-join($dateTransliteration/*:namePart, ', '),')') + else () + :) + ) + else + (: In all other situations, the name order is given-family; the difference is whether there is a space between the name parts and the order of name proper and the address. :) + (: Example: "Dr. Sigmund Freud (1856-1939)". :) + if ($nameOrder ne 'family-given') + (: If it is e.g. a Russian name. :) + then + concat( + if ($termsOfAddressTransliteration/string()) + then concat(', ', string-join($termsOfAddressTransli... [truncated message content] |