tool-gallery

Show, don’t tell

Initialize a new dse-static-cookiecutter instance

In this section we are going to build a lightweight static version of Die Ministerratsprotokolle 1848–1918

  1. go to https://github.com/acdh-oeaw/dse-static-cookiecutter
  2. read and follow the Quickstart-Section
  3. optional: install Python and Cookiecutter
    • e.g. use uv
  4. open a console
  5. initialize the project
     uvx cookiecutter gh:acdh-oeaw/dse-static-cookiecutter
    
  6. add project specific information (answer the questions)
    1. Q: [1/12] ‘directory_name’ This folder will be created and is going to hold your awesome code base (dse-static):
      • A: mrp-static
    2. Q: [2/12] Some nice title of your Project, will be displayed by default on the start page of your website (Digital Scholarly Editions Static Site Cookiecutter):
      • A: Die Ministerratsprotokolle 1848–1918 (statisch)
    3. Q: [3/12] Some short(er) title of your project; shows up by default in the nav bar (DSE Static-Site):
      • A: MRP (statisch)
    4. Q: [4/12] Default language of the project. Will be used as lang attribute in the html head elements. Can be customized later (de):
      • A: de
    5. Q: [5/12] Either your GitHub User Name, or if you want to host your code repo as part of GitHub Organisation, the organisation name. This information will be used to generate a link to the code repo of your application (acdh-oeaw):
      • A: acdh-tool-gallery
    6. Q: [6/12] You can write whatever URL to your code repo you want, or you take the default value which is a combination of the ‘github_org’ and ‘directory_name’. (https://github.com/acdh-tool-gallery/mrp-static):
      • A: https://github.com/acdh-tool-gallery/mrp-static
    7. Q: [7/12] The URL you have reserved for your website. (https://acdh-tool-gallery.github.io/mrp-static/):
      • A: https://acdh-tool-gallery.github.io/mrp-static/
    8. Q: [8/12] The ID of the Redmine-Service-Issue for you application. This is needed to generate an up to date imprint (18716):
      • A: 18716
    9. Q: [9/12] Should i18n be included to provide translations 1 - no; 2 - yes; Chose from [1/2] (1):
      • A: 1
    10. Q: [10/12] Ideally data and code is separated. If you want to use the code repo to store/curate the data as well, type ‘no’ (and just confirm the following questions); If you want to fetch the data from another GitHub Repo confirm, and pay a little more attention to the next questions. [y/n] (y)
      • A: y
    11. Q: [11/12] Where is the data for your app stored? Provide the name of a GitHub repo containing your data. Leave it blank to use dummy data or add your data to the code repo later. (dse-static-data):
      • A: mrp-data
    12. Q: [12/12] A GitHub repo where the data of you app can be found. The default value is a combination of the answers ‘github_repo’ and ‘data_dir’. (https://github.com/acdh-tool-gallery/mrp-data):
      • A: https://github.com/acdh-tool-gallery/mrp-data
  7. change into new created directory mrp-static
  8. run ./fetch_data.sh to download the data from https://github.com/acdh-tool-gallery/mrp-data into the current project
  9. run ant to build the website, i.e. converting TEI/XML-Documents into HTML-Files
  10. optional: change directory into html and start a webserver
     cd html && python -m http.server
    
  11. optional: visit http://127.0.0.1:8000/

Create GitHub Repo and make the initial commit

  1. create the GitHub repo that you named in Question 6 (https://github.com/acdh-tool-gallery/mrp-static)
  2. go to https://github.com/acdh-tool-gallery and click on the green button “New”
  3. fill out the form
    1. Repository name
      • mrp-static same as your answer to Question 1/12
    2. Description
      • static demo version of https://mrp.oeaw.ac.at/pages/index.html
    3. Choose visibility *
      • public (if private you’d need to pay to use GitHub-Actions and GitHub-Pages)
    4. leave the rest and click the green button “Create repository”
  4. go back to the console and make sure you are in the mrp-static folder
  5. run the following commands to initialize a git repo, link it to https://github.com/acdh-tool-gallery/mrp-static add, commit and push all files
     git init
     git add --all
     git commit -a -m "init commit"
     git branch -M main
     git remote add origin https://github.com/acdh-tool-gallery/mrp-static.git
     git push -u origin main
    

    this takes a short while because many files need to be processed/uploaded

  6. go to https://github.com/acdh-tool-gallery/mrp-static

Deploy the digital edition via GitHub Pages

  1. go to https://github.com/acdh-tool-gallery/mrp-static/settings/pages
  2. from the dropdown list Build and deployment select GitHub Actions
  3. go to https://github.com/acdh-tool-gallery/mrp-static/actions
  4. click on Deploy static content to Pages (on the left side)
  5. click on the grey button Run workflow
    • click on the green button Run workflow
  6. reload https://github.com/acdh-tool-gallery/mrp-static/actions/workflows/build.yml and wait for a yellow spinning circle. Click on it and watch how the app is going to be build and deployed (can take 1-2 minutes)
  7. when everything is done your app should be online under https://acdh-tool-gallery.github.io/mrp-static/

Develop/adapt/modify the digital edition

This section exemplifies some basic development best practices

correct document title

e.g. https://acdh-tool-gallery.github.io/mrp-static/toc.html makes not much sense

  1. check the data, e.g. https://acdh-tool-gallery.github.io/mrp-static/MRP-3-0-01-0-18670816-P-0044.xml
<titleStmt>
    <title level="s" type="desc">Digitale Edition</title>
    <title level="s" type="main" n="3">Die Protokolle des cisleithanischen Ministerrates 1867–1918</title>
    <title level="s" type="main" n="0"/>
    <title level="a" type="desc" n="044">Nr. 44 Ministerrat (19. Februar 1867–15. Dezember 1867)</title>
    <title level="m" type="main">Band I: 1867</title>
    <title level="m" type="main" n="01">Band 1</title>
    <title level="m" type="sub" n="0"/>
    <title level="m" type="sub">19. Februar 1867–15. Dezember 1867</title>
    <title level="m" type="dates" from="1867-02-19" to="1867-12-15"/>
    <meeting>
        <placeName>Wien</placeName>
        <orgName>Ministerrat</orgName>
        <date when="1867-08-16">1867-08-16</date>
    </meeting>
</titleStmt>
  1. hope for an easy to extract data point providing good title information
  2. <title level="a" type="desc" n="044">Nr. 44 Ministerrat (19. Februar 1867–15. Dezember 1867)</title> (actually created beforehand for this tool-gallery)
  3. open Oxygen-XML Editor
    1. Project » Open Project (ctrl + F2)
    2. search for mrp-static.xpr and open it
  4. open xslt/toc.xsl
  5. adapt the XPath to the title node
    • <xsl:value-of select=".//tei:titleStmt/tei:title[1]/text()"/> -> <xsl:value-of select=".//tei:titleStmt/tei:title[@level='a']/text()"/>
  6. build toc.html applying an already provided transformation scenario
    • open data/imprint.xml
    • click on the wrench symbol (or Document » Transformation » Apply Configure Transformation Scenario)
    • click Apply associated (1)
    • check the result in the opened browser window

using a more recent Saxon processor ` returns nothing; therefore the links and ID column in the table are empty

  1. replace
     <xsl:variable name="full_path">
         <xsl:value-of select="document-uri(/)"/>
     </xsl:variable>
    

    with

     <xsl:variable name="docId">
         <xsl:value-of select="document-uri(/)"/>
     </xsl:variable>
    
  2. replace the related code parts
     <xsl:value-of select="replace(tokenize($full_path, '/')[last()], '.xml', '.html')"/>
    

    with

     <xsl:value-of select="$docId"/>`
    
  3. rebuild toc.html (go to imprint.xml and hit strg+shift+T)

fix title in edition detail view

  1. open xslt/editions.xsl
  2. replace
     <xsl:variable name="doc_title">
         <xsl:value-of select=".//tei:titleStmt/tei:title[1]/text()"/>
     </xsl:variable>
    

    with

     <xsl:variable name="doc_title">
         <xsl:value-of select=".//tei:titleStmt/tei:title[@level='a']/text()"/>
     </xsl:variable>
    
  3. open e.g. data/editions/MRP-3-0-01-0-18670219-P-0001.xml and hit strg+shift+T (apply Transformation Scenario)
    1. there is no scenario attached to this file
    2. select “editions” (provided by the cookiecutter)
    3. click on edit » output » check Open in Browser
    4. click OK
    5. click Apply

persist changes and redeploy

  1. commit and push your changes (e.g. using Oxygen-Git Plugin)
  2. go to https://github.com/acdh-tool-gallery/mrp-static/actions/workflows/build.yml
  3. click Run workflow

Layout/Design

  1. adapt/customize xslt/partials/html_footer.xsl by replacing existing `<footer></footer> with the snippet below
     <footer class="footer mt-auto py-3 bg-body-tertiary">
         <div class="container-fluid pt-2">
             <div class="row justify-content-center">
                 <div class="col-lg-1 col-md-2 col-sm-2 col-xs-6 text-center">
                     <div>
                         <a href="https://www.oeaw.ac.at/acdh/">
                             <img src="images/logo.png" class="footer-logo" alt="ACDH Logo" title="ACDH Logo" />
                         </a>
                     </div>
                 </div>
                 <div class="col-lg-4 col-md-3 col-sm-3">
                     <div>
                         <p>
                             ACDH
                             <br />
                             Austrian Centre for Digital Humanities
                             <br />
                             Österreichische Akademie der Wissenschaften
                         </p>
                         <p>
                             Bäckerstraße 13
                             <br />
                             1010 Wien
                         </p>
                         <p>
                             T: +43 1 51581-2200
                             <br />
                             E: <a href="mailto:acdh-helpdesk@oeaw.ac.at">acdh-helpdesk@oeaw.ac.at</a>
                         </p>
                     </div>
                 </div>
                 <div class="col-lg-3 col-md-4 col-sm-3">
                     <div class="row">
                         <div>
                             <span class="fs-6">HELPDESK</span>
                             <br />
                             <p>ACDH betreibt einen Helpdesk mit Rat und Hilfestellung zu verschiedensten Fragen der Digital
                                 Humanities.
                             </p>
                             <p>
                                 <a href="mailto:acdh-helpdesk@oeaw.ac.at">e-Mail</a>
                             </p>
                         </div>
                     </div>
                     <div class="row">
                         <div class="col-lg-3 col-md-4 col-sm-3">
                             <div class="col-md-4">
                                 <a id="github-logo" title="GitHub" href="{$github_url}" class="nav-link" target="_blank">
                                     <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
                                         <path
                                             d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-4.466 19.59c-.405.078-.534-.171-.534-.384v-2.195c0-.747-.262-1.233-.55-1.481 1.782-.198 3.654-.875 3.654-3.947 0-.874-.312-1.588-.823-2.147.082-.202.356-1.016-.079-2.117 0 0-.671-.215-2.198.82-.64-.18-1.324-.267-2.004-.271-.68.003-1.364.091-2.003.269-1.528-1.035-2.2-.82-2.2-.82-.434 1.102-.16 1.915-.077 2.118-.512.56-.824 1.273-.824 2.147 0 3.064 1.867 3.751 3.645 3.954-.229.2-.436.552-.508 1.07-.457.204-1.614.557-2.328-.666 0 0-.423-.768-1.227-.825 0 0-.78-.01-.055.487 0 0 .525.246.889 1.17 0 0 .463 1.428 2.688.944v1.489c0 .211-.129.459-.528.385-3.18-1.057-5.472-4.056-5.472-7.59 0-4.419 3.582-8 8-8s8 3.581 8 8c0 3.533-2.289 6.531-5.466 7.59z">
                                         </path>
                                     </svg>
                                 </a>
                             </div>
                         </div>
                     </div>
                 </div>
                 <!-- .-->
             </div>
             <div class="text-center fs-6 fw-lighter">© Copyright OEAW | <a href="imprint.html">Imprint</a>
             </div>
         </div>
     </footer>
    
  2. adapt html/css/style.css by adding
     .footer-logo {
         max-width: 50%;
         max-height: 150px;
     }
    
  3. try out by converting any XML/TEI Document via Oxygen
  4. commit, push, redeploy

landing page (index.html)

  1. open xslt/index.xsl
  2. replace <main> ... </main> with
     <main class="flex-shrink-0 flex-grow-1">
         <div class="container col-xxl-8 pt-3">
             <div class="row flex-lg-row align-items-center g-5 py-5">
                 <div class="col-lg-6">
                     <h1 class="lh-base">
                         <span class="display-6"><xsl:value-of select="$project_short_title"/></span>
                         <br/>
                         <span class="display-5"><xsl:value-of select="$project_title"/></span>
                     </h1>
                     <p class="text-end">Demo Applikation, erstellt für die ACDH Tool-Gallery 11.3</p>
                     <p class="lead"> Die Daten stammen von: Stephan Kurz. (2024). oeaw-ministerratsprotokolle/mp-edition-data: v. 1.5 including CMR calendar data 1872–1914 (v.1.5). Zenodo. <a href="https://doi.org/10.5281/zenodo.11484662">https://doi.org/10.5281/zenodo.11484662</a></p>
                     <div class="d-grid gap-2 d-md-flex justify-content-md-start">
                         <a href="search.html" type="button" class="btn btn-primary btn-lg px-4 me-md-2">Volltextsuche</a>
                         <a href="toc.html" type="button" class="btn btn-outline-primary btn-lg px-4">Zu den Protokollen</a>
                     </div>
                 </div>
                 <div class="col-10 col-sm-8 col-lg-6">
                     <figure class="figure">
                         <img src="images/title-image.jpg" class="d-block mx-lg-auto img-fluid" alt="Friedrich Ferdinand Freiherr von Beust, via Wikimedia Commons" width="400" height="600" loading="lazy"/>
                         <figcaption class="pt-3 figure-caption">Friedrich Ferdinand Freiherr von Beust um 1860; von Autor/-in unbekannt - <a rel="nofollow" class="external free" href="http://www.aeiou.at/aeiou.encyclop.data.image.b/b417372a.jpg">http://www.aeiou.at/aeiou.encyclop.data.image.b/b417372a.jpg</a>, Gemeinfrei, <a href="https://commons.wikimedia.org/w/index.php?curid=1326691">Link</a></figcaption>
                     </figure>
                 </div>
             </div>
         </div>
     </main>
    
  3. download image and save it as html/images/title-image.jpg e.g. running
     curl -L "https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Friedrich_Ferdinand_von_Beust_1860.jpg/594px-Friedrich_Ferdinand_von_Beust_1860.jpg?download" -o html/images/title-image.jpg
    
  4. add, commit and push
     git add --all
     git commit -a -m "feat(design): landing page"
     git push origin main
    

global settings (params.xsl)

Some answers from the initialisation process are stored in xslt/partials/params.xsl.

  1. replace
     <xsl:param name="project_short_title">MRP (statisch)</xsl:param>
    

    with

     <xsl:param name="project_short_title">MRP</xsl:param>
    
  2. rebuild e.g. index.html
  3. check http://127.0.0.1:8000/
    • make sure you have a development server up and running
    • MRP (statisch) -> MRP

zotero

Expose metadata via HTML-Meta tags for Zotero

  1. modify xslt/partials/zotero.xsl
    1. replace existing <xsl:template name="zoterMetaTags">...</xsl:template> with
       <xsl:template name="zoterMetaTags">
           <xsl:param name="zoteroTitle" select="false()"></xsl:param>
           <xsl:param name="pageId" select="''"></xsl:param>
           <xsl:param name="customUrl" select="$base_url"></xsl:param>
           <xsl:variable name="fullUrl" select="concat($customUrl, $pageId)"/>
           <xsl:if test="$zoteroTitle">
               <meta name="citation_title" content="{$zoteroTitle}"/>
           </xsl:if>
           <meta name="citation_editors" content="Franz Adlgasser; Anatol Schmied-Kowarzik"/>
           <meta name="citation_publisher" content="Österreichische Akademie der Wissenschaften"/>
           <meta name="citation_book_title" content="{$project_title}"/>
           <meta name="citation_public_url" content="{$fullUrl}"/>
           <meta name="citation_date" content="2025"/>
       </xsl:template>
      
  2. for edition-detail view, add editor of current document
    1. inspect the source-data, e.g. data/editions/MRP-3-0-01-0-18670301-P-0006.xml
       <editor key="http://d-nb.info/gnd/109427793" role="editor" ref="#editor_Malfer">
       <persName>
           <forename>Stefan</forename>
           <surname>Malfèr</surname>
       </persName>
       <affiliation key="http://d-nb.info/gnd/1202798799" from="2020-01-01">Österreichische Akademie der Wissenschaften, Institute for Habsburg and Balkan Studies</affiliation>
       <affiliation key="http://d-nb.info/gnd/1047201437" to="2019-12-31">Österreichische Akademie der Wissenschaften, Institut für Neuzeit- und Zeitgeschichtsforschung</affiliation>
       <!-- Legacy Doppelung nur bei Malfèr, weil da das Institut noch INZ hieß -->
       </editor>
      
    2. extract editor-name in editions.xsl and replace
       <!-- Provide the names of the authors/editors of the current unit, ideally fetched from the data via xslt or hard coded as below -->
       <meta name="citation_author" content="Foo, Bar"/>
       <meta name="citation_author" content="Bar, Foo"/> 
      

      with

       <xsl:for-each select=".//tei:titleStmt/tei:editor/tei:persName">
           <meta name="citation_author" content="{string-join(.//text())}"/>
       </xsl:for-each>
      
  3. Transform data/editions/MRP-3-0-01-0-18670301-P-0006.xml with xslt/editions.xsl (ant)
  4. go to http://127.0.0.1:8000/MRP-3-0-01-0-18670301-P-0006.html and fetch Zotero metadata via Zotero browser plug-in
  5. check fetched item in Zotero

    Stefan Malfèr / Franz Adlgasser / Anatol Schmied-Kowarzik: Nr. 1 Ministerrat (19. Februar 1867–15. Dezember 1867). In: MRP (statisch). 2025. [https://acdh-tool-gallery.github.io/mrp-static/MRP-3-0-01-0-18670219-P-0001.html]

Entities

customize listperson.html

  1. open xslt/listperson.xsl
  2. open data/indices/listperson.xml
  3. analyze data/indices/listperson.xml
    • z.B. <person xml:id="mpr2049">
    • occupation
    • mentions (tei:noteGrp)
  4. add new columns to xslt/listperson.xsl
     <th scope="col" tabulator-headerFilter="input">Tätigkeit</th>
     <th scope="col" tabulator-headerFilter="input">Erwähnungen</th>
    
  5. populate those columns
     <td>
         <xsl:value-of select="string-join(.//tei:occupation, ', ')"/>
     </td>
     <td>
         <xsl:value-of select="count(.//tei:noteGrp/tei:note[@type='mentions'])"/>
     </td>
    

[!IMPORTANT]
Denormalisation! Make sure that your data provides the information needed to link from an index file to the actual mentions.

fix Erwähnt in

  1. in xslt/listperson.xsl change
     <xsl:if test="./tei:noteGrp/tei:note[@type = 'mentions']">
         <dt>Erwähnt in</dt>
         <dd>
             <xsl:for-each select="./tei:noteGrp/tei:note[@type = 'mentions']">
                 <a href="{replace(@target, '.xml', '.html')}">
                     <xsl:value-of select="./text()"/>
                 </a>
             </xsl:for-each>
         </dd>
     </xsl:if>
    

    to

     <xsl:if test="./tei:noteGrp/tei:note[@type = 'mentions']">
         <dt>Erwähnt in</dt>
         <xsl:for-each select="./tei:noteGrp/tei:note[@type = 'mentions']">
             <dd>
                 <a href="{replace(@target, '.xml', '.html')}">
                     <xsl:value-of select="./text()"/>
                 </a>
             </dd>
         </xsl:for-each>
     </xsl:if>
    
  2. style <dl> by adding
     dd {
         margin-left: 1rem;
     }
    

    to html/css/style.css

Typesense

dse-static-cookiecutter’s typesense integration

building the index

  1. make sure your local Typesense is running / you set proper environment variables
  2. check pyscripts/make_ts_index.py if it fits your data
    1. it does not, we want to have another document title: replace
       record["title"] = doc.any_xpath(".//tei:titleStmt/tei:title[1]")[0].text
      

      with

       record["title"] = doc.any_xpath(".//tei:titleStmt/tei:title[@level='a']")[0].text
      
  3. optional: check if you have the required packages installed (see pyproject.toml)
  4. run uv run pyscripts/make_ts_index.py from mrp-static
  5. check the created index, e.g. via Typesense-Dashboard
    1. install and run it via docker
       docker run -d -p 80:80 ghcr.io/bfritscher/typesense-dashboard:latest
      
    2. go to http://127.0.0.1
  6. include the building of the search index in the deployment workflow
    1. replace
       - name: Build the app
         run: ant
       - name: Setup Pages
      

      with

       - name: Build the app
         run: ant
       - name: Create search index
         run: python pyscripts/make_ts_index.py
       - name: Setup Pages
      
  7. create a repository (or organisation) secret called TYPESENSE_API_KEY
    1. go to https://github.com/acdh-tool-gallery/mrp-static/settings/secrets/actions
    2. click the green button New repository secret
    3. Name -> TYPESENSE_API_KEY
    4. Secret -> your Typesense api key
  8. commit, push, redeploy, and check if the index was created

building the search interface

Once the index is created, we can create a search page in our website. The search page is created using instantsearch.js and typesense-instantsearch-adapter

  1. create a search API-KEY for your newly created collection mrp-static
    1. go to your Typesense-Dashboard
    2. click on API Keys
    3. click on CREATE API KEY
    4. click on SEARCH KEY EXAMPLE
    5. replace companies with mrp-static
      • dse-static-cookiecutter names the Typesense collection same as directory_name you provided on initialization
    6. click CREATE API KEY
    7. copy the key
    8. paste it into html/js/search.js
      • replace
         const apiKey = "0drlT8CHD6T9z8QxQjYXvSWT2dZ75nPv"; /* change this */
        

        with

         const apiKey = "the new key"; /* change this */
        
  2. check if http://127.0.0.1:8000/search.html actually works (it won’t)
  3. check why:

    Error: 404 - Could not find a facet field named bibl_entities.label in the schema.

    Error: 404 - Could not find a facet field named bibl_entities.label in the schema.

  4. check if the Error tells the truth by inspecting the created collection schema via Typesense-Dashboard
    • yes the machine is correct
  5. fix the code
    1. (re)open html/js/search.js
    2. search for something like bibl_entities
    3. remove the related code snippets
       ${hit.bibl_entities.map(
       (item) =>
       html`<a href="${item.id}.html" class="pe-2 custom-link"><i class="bi bi-book pe-1"></i>${item.label}</a>`
       )}
       <br />
       ...
       instantsearch.widgets.panel({
       collapsed: ({ state }) => {
       return state.query.length === 0;
       },
       templates: {
       header: "Literatur",
       },
       })(instantsearch.widgets.refinementList)({
       container: "#rf-works",
       attribute: "bibl_entities.label",
       searchable: true,
       showMore: true,
       showMoreLimit: 50,
       limit: 10,
       searchablePlaceholder: "Suche nach Literatur",
       cssClasses: DEFAULT_CSS_CLASSES,
       }),
      
  6. save and check again http://127.0.0.1:8000/search.html
  7. commit, push, redeploy

Possible next steps

Plug-ins

Bigger picture

dse-static-cookiecutter tries to follow three principles static, automatic, generic so that your edition can be kept online forever.

static

No database, no application server needed; easy to host/maintain (just a bunch of HTML files)

automatic

All code needed to process, build, deploy should be managed by a single script/file; no more “But it runs on my machine”. This is achieved by .github/workflows/build.yml and as backup by docker/Dockerfile

generic

cookiecutter is used to create a similar starting point for your individual application. The idea is: You know one, you know all

Actual pictures

ACDH infrastructure (not only) for digital editions

ACDH-Infrastructure

What keeps running without ACDH

Without ACDH-Infrastructure