diff --git a/controller.xql b/controller.xql index e206cad0a..abf2138bc 100644 --- a/controller.xql +++ b/controller.xql @@ -40,6 +40,13 @@ console:log('exist:path: ' || $exist:path) , :) +let $if-modified-since := request:get-header("If-Modified-Since") +return + if ($if-modified-since) then + request:set-attribute("if-modified-since", $if-modified-since) + else + (), + if ($exist:path eq '') then diff --git a/modules/app.xqm b/modules/app.xqm index da32bd4e0..f441b1f10 100644 --- a/modules/app.xqm +++ b/modules/app.xqm @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; module namespace app="http://history.state.gov/ns/site/hsg/templates"; @@ -151,6 +151,20 @@ function app:handle-error($node as node(), $model as map(*), $code as xs:int?) { () }; +declare function app:format-http-date($dateTime as xs:dateTime) as xs:string { + $dateTime + => adjust-dateTime-to-timezone(xs:dayTimeDuration("PT0H")) + => format-dateTime("[FNn,*-3], [D01] [MNn,*-3] [Y0001] [H01]:[m01]:[s01] [Z0000]", "en", (), ()) +}; + +declare function app:set-last-modified($last-modified as xs:dateTime) { + response:set-header("Last-Modified", app:format-http-date($last-modified)) +}; + +declare function app:set-created($created as xs:dateTime) { + response:set-header("Created", app:format-http-date($created)) +}; + declare function app:uri($node as node(), $model as map(*)) { {request:get-attribute("hsg-shell.path")} }; diff --git a/modules/config.xqm b/modules/config.xqm index 04d41d8ed..bf46f68e5 100644 --- a/modules/config.xqm +++ b/modules/config.xqm @@ -115,6 +115,32 @@ declare variable $config:PUBLICATIONS := map { "frus": map { "collection": $config:FRUS_VOLUMES_COL, + "document-last-modified": function($document-id) { + ( + xmldb:last-modified($config:FRUS_VOLUMES_COL, $document-id || '.xml'), + (: for volumes that we do not have as TEI yet, fall back on volume metadata :) + xmldb:last-modified($config:FRUS_METADATA_COL, $document-id || '.xml') + )[1] + }, + "section-last-modified": function($document-id, $section-id) { + ( + xmldb:last-modified($config:FRUS_VOLUMES_COL, $document-id || '.xml'), + xmldb:last-modified($config:FRUS_METADATA_COL, $document-id || '.xml') + )[1] + }, + "document-created": function($document-id) { + ( + xmldb:created($config:FRUS_VOLUMES_COL, $document-id || '.xml'), + (: for volumes that we do not have as TEI yet, fall back on volume metadata :) + xmldb:created($config:FRUS_METADATA_COL, $document-id || '.xml') + )[1] + }, + "section-created": function($document-id, $section-id) { + ( + xmldb:created($config:FRUS_VOLUMES_COL, $document-id || '.xml'), + xmldb:created($config:FRUS_METADATA_COL, $document-id || '.xml') + )[1] + }, "select-document": function($document-id) { doc($config:FRUS_VOLUMES_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { let $node := doc($config:FRUS_VOLUMES_COL || '/' || $document-id || '.xml')/id($section-id) @@ -155,6 +181,10 @@ declare variable $config:PUBLICATIONS := }, "buildings": map { "collection": $config:BUILDINGS_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:BUILDINGS_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:BUILDINGS_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:BUILDINGS_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:BUILDINGS_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:BUILDINGS_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:BUILDINGS_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/departmenthistory/" || string-join(($document-id, $section-id), '/') }, @@ -171,6 +201,10 @@ declare variable $config:PUBLICATIONS := }, "conferences": map { "collection": $config:CONFERENCES_ARTICLES_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:CONFERENCES_ARTICLES_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:CONFERENCES_ARTICLES_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:CONFERENCES_ARTICLES_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:CONFERENCES_ARTICLES_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:CONFERENCES_ARTICLES_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:CONFERENCES_ARTICLES_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/conferences/" || string-join(($document-id, $section-id), '/') }, @@ -193,7 +227,11 @@ declare variable $config:PUBLICATIONS := }, "countries": map { "collection": $config:COUNTRIES_ARTICLES_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:COUNTRIES_ARTICLES_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:COUNTRIES_ARTICLES_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:COUNTRIES_ARTICLES_COL || '/' || $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:COUNTRIES_ARTICLES_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:COUNTRIES_ARTICLES_COL, $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:COUNTRIES_ARTICLES_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/countries/" || string-join(($document-id, $section-id), '/') }, "odd": "frus.odd", @@ -203,6 +241,10 @@ declare variable $config:PUBLICATIONS := }, "countries-issues": map { "collection": $config:COUNTRIES_ISSUES_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:COUNTRIES_ISSUES_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:COUNTRIES_ISSUES_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:COUNTRIES_ISSUES_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:COUNTRIES_ISSUES_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:COUNTRIES_ISSUES_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:COUNTRIES_ISSUES_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/countries/issues/" || string-join(($document-id, $section-id), '/') }, @@ -213,6 +255,10 @@ declare variable $config:PUBLICATIONS := }, "archives": map { "collection": $config:ARCHIVES_ARTICLES_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:ARCHIVES_ARTICLES_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:ARCHIVES_ARTICLES_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:ARCHIVES_ARTICLES_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:ARCHIVES_ARTICLES_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:ARCHIVES_ARTICLES_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:ARCHIVES_ARTICLES_COL || '/' || $document-id || '.xml')//tei:body }, "html-href": function($document-id, $section-id) { "$app/countries/" || string-join(($document-id, $section-id), '/') }, @@ -222,6 +268,10 @@ declare variable $config:PUBLICATIONS := }, "articles": map { "collection": $config:FRUS_HISTORY_ARTICLES_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:FRUS_HISTORY_ARTICLES_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:FRUS_HISTORY_ARTICLES_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:FRUS_HISTORY_ARTICLES_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:FRUS_HISTORY_ARTICLES_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:FRUS_HISTORY_ARTICLES_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:FRUS_HISTORY_ARTICLES_COL || '/' || $document-id || '.xml')//tei:body }, "html-href": function($document-id, $section-id) { "$app/frus-history/" || string-join(($document-id, $section-id), '/') }, @@ -240,6 +290,10 @@ declare variable $config:PUBLICATIONS := }, "people": map { "collection": $config:SECRETARY_BIOS_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:SECRETARY_BIOS_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:SECRETARY_BIOS_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:SECRETARY_BIOS_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:SECRETARY_BIOS_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:SECRETARY_BIOS_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:SECRETARY_BIOS_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/departmenthistory/people/" || string-join(($document-id, $section-id), '/') }, @@ -285,6 +339,10 @@ declare variable $config:PUBLICATIONS := }, "milestones": map { "collection": $config:MILESTONES_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:MILESTONES_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:MILESTONES_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:MILESTONES_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:MILESTONES_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:MILESTONES_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:MILESTONES_COL|| '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/milestones/" || string-join(($document-id, $section-id), '/') }, @@ -295,6 +353,10 @@ declare variable $config:PUBLICATIONS := }, "short-history": map { "collection": $config:SHORT_HISTORY_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:SHORT_HISTORY_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:SHORT_HISTORY_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:SHORT_HISTORY_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:SHORT_HISTORY_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:SHORT_HISTORY_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:SHORT_HISTORY_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/departmenthistory/" || string-join(($document-id, $section-id), '/') }, @@ -305,6 +367,10 @@ declare variable $config:PUBLICATIONS := }, "timeline": map { "collection": $config:ADMINISTRATIVE_TIMELINE_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:ADMINISTRATIVE_TIMELINE_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:ADMINISTRATIVE_TIMELINE_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:ADMINISTRATIVE_TIMELINE_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:ADMINISTRATIVE_TIMELINE_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:ADMINISTRATIVE_TIMELINE_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:ADMINISTRATIVE_TIMELINE_COL || '/' || $document-id || '.xml')/id('chapter_' || $section-id) }, "html-href": function($document-id, $section-id) { "$app/departmenthistory/" || string-join(($document-id, substring-after($section-id, 'chapter_')), '/') }, @@ -316,6 +382,10 @@ declare variable $config:PUBLICATIONS := }, "faq": map { "collection": $config:FAQ_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:FAQ_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:FAQ_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:FAQ_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:FAQ_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:FAQ_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:FAQ_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/about/" || string-join(($document-id, $section-id), '/') }, @@ -325,6 +395,10 @@ declare variable $config:PUBLICATIONS := }, "hac": map { "collection": $config:HAC_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:HAC_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:HAC_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:HAC_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:HAC_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:HAC_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:HAC_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/about/" || string-join(($document-id, $section-id), '/') }, @@ -334,6 +408,10 @@ declare variable $config:PUBLICATIONS := }, "education": map { "collection": $config:EDUCATION_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:EDUCATION_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:EDUCATION_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:EDUCATION_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:EDUCATION_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:EDUCATION_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:EDUCATION_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/education/modules/" || string-join(($document-id, $section-id), '#') }, @@ -346,6 +424,10 @@ declare variable $config:PUBLICATIONS := }, "frus-history-monograph": map { "collection": $config:FRUS_HISTORY_MONOGRAPH_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:FRUS_HISTORY_MONOGRAPH_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:FRUS_HISTORY_MONOGRAPH_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:FRUS_HISTORY_MONOGRAPH_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:FRUS_HISTORY_MONOGRAPH_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:FRUS_HISTORY_MONOGRAPH_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { let $target-section-id := @@ -392,6 +474,10 @@ declare variable $config:PUBLICATIONS := }, "vietnam-guide": map { "collection": $config:VIETNAM_GUIDE_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:VIETNAM_GUIDE_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:VIETNAM_GUIDE_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:VIETNAM_GUIDE_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:VIETNAM_GUIDE_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:VIETNAM_GUIDE_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:VIETNAM_GUIDE_COL || '/' || $document-id || '.xml') }, "html-href": function($document-id, $section-id) { "$app/historicaldocuments/" || string-join(($document-id, $section-id), '/') }, @@ -401,6 +487,10 @@ declare variable $config:PUBLICATIONS := }, "views-from-the-embassy": map { "collection": $config:VIEWS_FROM_EMBASSY_COL, + "document-last-modified": function($document-id) { xmldb:last-modified($config:VIEWS_FROM_EMBASSY_COL, $document-id || '.xml') }, + "section-last-modified": function($document-id, $section-id) { xmldb:last-modified($config:VIEWS_FROM_EMBASSY_COL, $document-id || '.xml') }, + "document-created": function($document-id) { xmldb:created($config:VIEWS_FROM_EMBASSY_COL, $document-id || '.xml') }, + "section-created": function($document-id, $section-id) {xmldb:created($config:VIEWS_FROM_EMBASSY_COL, $document-id || '.xml') }, "select-document": function($document-id) { doc($config:VIEWS_FROM_EMBASSY_COL || '/' || $document-id || '.xml') }, "select-section": function($document-id, $section-id) { doc($config:VIEWS_FROM_EMBASSY_COL || '/' || $document-id || '.xml')/id($section-id) }, "html-href": function($document-id, $section-id) { "$app/departmenthistory/wwi" }, diff --git a/modules/pages.xqm b/modules/pages.xqm index 45182aec7..630286ac3 100644 --- a/modules/pages.xqm +++ b/modules/pages.xqm @@ -44,25 +44,83 @@ function pages:load($node as node(), $model as map(*), $publication-id as xs:str $section-id as xs:string?, $view as xs:string, $ignore as xs:boolean) { let $log := console:log("loading publication-id: " || $publication-id || " document-id: " || $document-id || " section-id: " || $section-id ) - let $content := map { - "data": - if (exists($publication-id) and exists($document-id)) then - pages:load-xml($publication-id, $document-id, $section-id, $view, $ignore) - else (), - "publication-id": $publication-id, - "document-id": $document-id, - "section-id": $section-id, - "view": $view, - "base-path": - (: allow for pages that don't have $config:PUBLICATIONS?select-document defined :) - if (exists($publication-id) and map:contains(map:get($config:PUBLICATIONS, $publication-id), 'base-path')) then - map:get($config:PUBLICATIONS, $publication-id)?base-path($document-id, $section-id) - else (), - "odd": if (exists($publication-id)) then map:get($config:PUBLICATIONS, $publication-id)?transform else $config:odd-transform-default - } - + let $last-modified := + if (exists($publication-id) and exists($document-id)) then + pages:last-modified($publication-id, $document-id, $section-id) + else + () + let $if-modified-since := try { request:get-attribute("if-modified-since") => parse-ietf-date() } catch * { () } + let $should-return-304 := + if (exists($last-modified) and exists($if-modified-since)) then + $if-modified-since ge + $last-modified + (: For the purpose of comparing the resource's last modified date with the If-Modified-Since + : header supplied by the client, we must truncate any milliseconds from the last modified date. + : This is because HTTP-date is only specific to the second. + : @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 :) + => format-dateTime("[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01][Z]") + => xs:dateTime() + else + () + let $created := + if (exists($publication-id) and exists($document-id)) then + (: No need to truncate creation date; it'll be serialized in view.xql :) + pages:created($publication-id, $document-id, $section-id) + else + () return - templates:process($node/*, map:merge(($model, $content))) + (: if the "If-Modified-Since" header in the client request is later than the + : last-modified date, then halt further processing of the templates and simply + : return a 304 response. :) + if ($should-return-304) then + ( + response:set-status-code(304), + app:set-last-modified($last-modified) + ) + else + let $content := map { + "data": + if (exists($publication-id) and exists($document-id)) then + pages:load-xml($publication-id, $document-id, $section-id, $view, $ignore) + else (), + "publication-id": $publication-id, + "document-id": $document-id, + "section-id": $section-id, + "view": $view, + "base-path": + (: allow for pages that do not have $config:PUBLICATIONS?select-document defined :) + (: ... TODO: I do not see any such cases in config:PUBLICATIONS! Check if OK to remove this entry? - JW :) + if (exists($publication-id) and map:contains(map:get($config:PUBLICATIONS, $publication-id), 'base-path')) then + map:get($config:PUBLICATIONS, $publication-id)?base-path($document-id, $section-id) + else (), + "odd": if (exists($publication-id)) then map:get($config:PUBLICATIONS, $publication-id)?transform else $config:odd-transform-default + } + + return + ( + if (exists($last-modified) and exists($created)) then + ( + request:set-attribute("hsgshell.last-modified", $last-modified), + request:set-attribute("hsgshell.created", $created) + ) + else + (), + templates:process($node/*, map:merge(($model, $content))) + ) +}; + +declare function pages:last-modified($publication-id as xs:string, $document-id as xs:string, $section-id as xs:string?) { + if ($section-id) then + map:get($config:PUBLICATIONS, $publication-id)?section-last-modified($document-id, $section-id) + else + map:get($config:PUBLICATIONS, $publication-id)?document-last-modified($document-id) +}; + +declare function pages:created($publication-id as xs:string, $document-id as xs:string, $section-id as xs:string?) { + if ($section-id) then + map:get($config:PUBLICATIONS, $publication-id)?section-created($document-id, $section-id) + else + map:get($config:PUBLICATIONS, $publication-id)?document-created($document-id) }; declare function pages:load-xml($publication-id as xs:string, $document-id as xs:string, $section-id as xs:string?, $view as xs:string) { diff --git a/modules/view.xql b/modules/view.xql index b9619292a..a4ec2abd6 100644 --- a/modules/view.xql +++ b/modules/view.xql @@ -56,4 +56,16 @@ let $lookup := function($functionName as xs:string, $arity as xs:int) { :) let $content := request:get-data() return - templates:apply($content, $lookup, (), $config) + ( + templates:apply($content, $lookup, (), $config), + let $last-modified := request:get-attribute("hsgshell.last-modified") + let $created := request:get-attribute("hsgshell.created") + return + if (exists($last-modified) and exists($created)) then + ( + app:set-last-modified($last-modified), + app:set-created($created) + ) + else + () + ) \ No newline at end of file