| | <!DOCTYPE html> |
| | <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head> |
| |
|
| | <meta charset="utf-8"> |
| | <meta name="generator" content="quarto-1.8.27"> |
| |
|
| | <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> |
| |
|
| |
|
| | <title>2 Structured Document Processing – AI Design Patterns for GLAM</title> |
| | <style> |
| | code{white-space: pre-wrap;} |
| | span.smallcaps{font-variant: small-caps;} |
| | div.columns{display: flex; gap: min(4vw, 1.5em);} |
| | div.column{flex: auto; overflow-x: auto;} |
| | div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} |
| | ul.task-list{list-style: none;} |
| | ul.task-list li input[type="checkbox"] { |
| | width: 0.8em; |
| | margin: 0 0.8em 0.2em -1em; |
| | vertical-align: middle; |
| | } |
| | </style> |
| |
|
| |
|
| | <script src="../../site_libs/quarto-nav/quarto-nav.js"></script> |
| | <script src="../../site_libs/quarto-nav/headroom.min.js"></script> |
| | <script src="../../site_libs/clipboard/clipboard.min.js"></script> |
| | <script src="../../site_libs/quarto-search/autocomplete.umd.js"></script> |
| | <script src="../../site_libs/quarto-search/fuse.min.js"></script> |
| | <script src="../../site_libs/quarto-search/quarto-search.js"></script> |
| | <meta name="quarto:offset" content="../../"> |
| | <link href="../../patterns/structured-generation/vlm-structured-generation.html" rel="next"> |
| | <link href="../../patterns/what-is-an-ai-pattern.html" rel="prev"> |
| | <script src="../../site_libs/quarto-html/quarto.js" type="module"></script> |
| | <script src="../../site_libs/quarto-html/tabsets/tabsets.js" type="module"></script> |
| | <script src="../../site_libs/quarto-html/axe/axe-check.js" type="module"></script> |
| | <script src="../../site_libs/quarto-html/popper.min.js"></script> |
| | <script src="../../site_libs/quarto-html/tippy.umd.min.js"></script> |
| | <script src="../../site_libs/quarto-html/anchor.min.js"></script> |
| | <link href="../../site_libs/quarto-html/tippy.css" rel="stylesheet"> |
| | <link href="../../site_libs/quarto-html/quarto-syntax-highlighting-ed96de9b727972fe78a7b5d16c58bf87.css" rel="stylesheet" id="quarto-text-highlighting-styles"> |
| | <script src="../../site_libs/bootstrap/bootstrap.min.js"></script> |
| | <link href="../../site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet"> |
| | <link href="../../site_libs/bootstrap/bootstrap-27c261d06b905028a18691de25d09dde.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light"> |
| | <script id="quarto-search-options" type="application/json">{ |
| | "location": "sidebar", |
| | "copy-button": false, |
| | "collapse-after": 3, |
| | "panel-placement": "start", |
| | "type": "textbox", |
| | "limit": 50, |
| | "keyboard-shortcut": [ |
| | "f", |
| | "/", |
| | "s" |
| | ], |
| | "show-item-context": false, |
| | "language": { |
| | "search-no-results-text": "No results", |
| | "search-matching-documents-text": "matching documents", |
| | "search-copy-link-title": "Copy link to search", |
| | "search-hide-matches-text": "Hide additional matches", |
| | "search-more-match-text": "more match in this document", |
| | "search-more-matches-text": "more matches in this document", |
| | "search-clear-button-title": "Clear", |
| | "search-text-placeholder": "", |
| | "search-detached-cancel-button-title": "Cancel", |
| | "search-submit-button-title": "Submit", |
| | "search-label": "Search" |
| | } |
| | }</script> |
| | <script src="../../site_libs/quarto-diagram/mermaid.min.js"></script> |
| | <script src="../../site_libs/quarto-diagram/mermaid-init.js"></script> |
| | <link href="../../site_libs/quarto-diagram/mermaid.css" rel="stylesheet"> |
| |
|
| |
|
| | </head> |
| |
|
| | <body class="nav-sidebar floating quarto-light"> |
| |
|
| | <div id="quarto-search-results"></div> |
| | <header id="quarto-header" class="headroom fixed-top"> |
| | <nav class="quarto-secondary-nav"> |
| | <div class="container-fluid d-flex"> |
| | <button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> |
| | <i class="bi bi-layout-text-sidebar-reverse"></i> |
| | </button> |
| | <nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html">Structured Information Extraction</a></li><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html"><span class="chapter-number">2</span> <span class="chapter-title">Structured Document Processing</span></a></li></ol></nav> |
| | <a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> |
| | </a> |
| | <button type="button" class="btn quarto-search-button" aria-label="Search" onclick="window.quartoOpenSearch();"> |
| | <i class="bi bi-search"></i> |
| | </button> |
| | </div> |
| | </nav> |
| | </header> |
| | |
| | <div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article"> |
| | |
| | <nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation floating overflow-auto"> |
| | <div class="pt-lg-2 mt-2 text-left sidebar-header"> |
| | <div class="sidebar-title mb-0 py-0"> |
| | <a href="../../">AI Design Patterns for GLAM</a> |
| | </div> |
| | </div> |
| | <div class="mt-2 flex-shrink-0 align-items-center"> |
| | <div class="sidebar-search"> |
| | <div id="quarto-search" class="" title="Search"></div> |
| | </div> |
| | </div> |
| | <div class="sidebar-menu-container"> |
| | <ul class="list-unstyled mt-1"> |
| | <li class="sidebar-item"> |
| | <div class="sidebar-item-container"> |
| | <a href="../../index.html" class="sidebar-item-text sidebar-link"> |
| | <span class="menu-text">Welcome</span></a> |
| | </div> |
| | </li> |
| | <li class="sidebar-item sidebar-item-section"> |
| | <span class="sidebar-item-text sidebar-link text-start"> |
| | <span class="menu-text">Beyond Chat Interfaces to Collections?</span></span> |
| | </li> |
| | <li class="sidebar-item sidebar-item-section"> |
| | <div class="sidebar-item-container"> |
| | <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true"> |
| | <span class="menu-text">Design Patterns</span></a> |
| | <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-2" role="navigation" aria-expanded="true" aria-label="Toggle section"> |
| | <i class="bi bi-chevron-right ms-2"></i> |
| | </a> |
| | </div> |
| | <ul id="quarto-sidebar-section-2" class="collapse list-unstyled sidebar-section depth1 show"> |
| | <li class="sidebar-item"> |
| | <div class="sidebar-item-container"> |
| | <a href="../../patterns/what-is-an-ai-pattern.html" class="sidebar-item-text sidebar-link"> |
| | <span class="menu-text"><span class="chapter-number">1</span> <span class="chapter-title">What is an AI Pattern?</span></span></a> |
| | </div> |
| | </li> |
| | </ul> |
| | </li> |
| | <li class="sidebar-item sidebar-item-section"> |
| | <div class="sidebar-item-container"> |
| | <a class="sidebar-item-text sidebar-link text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true"> |
| | <span class="menu-text">Structured Information Extraction</span></a> |
| | <a class="sidebar-item-toggle text-start" data-bs-toggle="collapse" data-bs-target="#quarto-sidebar-section-3" role="navigation" aria-expanded="true" aria-label="Toggle section"> |
| | <i class="bi bi-chevron-right ms-2"></i> |
| | </a> |
| | </div> |
| | <ul id="quarto-sidebar-section-3" class="collapse list-unstyled sidebar-section depth1 show"> |
| | <li class="sidebar-item"> |
| | <div class="sidebar-item-container"> |
| | <a href="../../patterns/structured-generation/intro.html" class="sidebar-item-text sidebar-link active"> |
| | <span class="menu-text"><span class="chapter-number">2</span> <span class="chapter-title">Structured Document Processing</span></span></a> |
| | </div> |
| | </li> |
| | <li class="sidebar-item"> |
| | <div class="sidebar-item-container"> |
| | <a href="../../patterns/structured-generation/vlm-structured-generation.html" class="sidebar-item-text sidebar-link"> |
| | <span class="menu-text"><span class="chapter-number">3</span> <span class="chapter-title">Structured Information Extraction with Vision Language Models</span></span></a> |
| | </div> |
| | </li> |
| | </ul> |
| | </li> |
| | </ul> |
| | </div> |
| | </nav> |
| | <div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div> |
| | |
| | <div id="quarto-margin-sidebar" class="sidebar margin-sidebar"> |
| | <nav id="TOC" role="doc-toc" class="toc-active"> |
| | <h2 id="toc-title">Table of contents</h2> |
| | |
| | <ul> |
| | <li><a href="#the-challenge" id="toc-the-challenge" class="nav-link active" data-scroll-target="#the-challenge"><span class="header-section-number">2.1</span> The Challenge</a></li> |
| | <li><a href="#dont-we-just-need-ocr" id="toc-dont-we-just-need-ocr" class="nav-link" data-scroll-target="#dont-we-just-need-ocr"><span class="header-section-number">2.2</span> Don’t we just need OCR?</a></li> |
| | <li><a href="#solution-overview" id="toc-solution-overview" class="nav-link" data-scroll-target="#solution-overview"><span class="header-section-number">2.3</span> Solution Overview</a> |
| | <ul class="collapse"> |
| | <li><a href="#what-this-pattern-looks-like" id="toc-what-this-pattern-looks-like" class="nav-link" data-scroll-target="#what-this-pattern-looks-like"><span class="header-section-number">2.3.1</span> What this pattern looks like</a></li> |
| | </ul></li> |
| | <li><a href="#when-to-use-this-pattern" id="toc-when-to-use-this-pattern" class="nav-link" data-scroll-target="#when-to-use-this-pattern"><span class="header-section-number">2.4</span> When to Use This Pattern</a></li> |
| | </ul> |
| | </nav> |
| | </div> |
| | |
| | <main class="content" id="quarto-document-content"> |
| |
|
| | <header id="title-block-header" class="quarto-title-block default"><nav class="quarto-page-breadcrumbs quarto-title-breadcrumbs d-none d-lg-block" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html">Structured Information Extraction</a></li><li class="breadcrumb-item"><a href="../../patterns/structured-generation/intro.html"><span class="chapter-number">2</span> <span class="chapter-title">Structured Document Processing</span></a></li></ol></nav> |
| | <div class="quarto-title"> |
| | <h1 class="title"><span class="chapter-number">2</span> <span class="chapter-title">Structured Document Processing</span></h1> |
| | </div> |
| |
|
| |
|
| |
|
| | <div class="quarto-title-meta"> |
| |
|
| | |
| | |
| | |
| | </div> |
| | |
| |
|
| |
|
| | </header> |
| |
|
| |
|
| | <section id="the-challenge" class="level2" data-number="2.1"> |
| | <h2 data-number="2.1" class="anchored" data-anchor-id="the-challenge"><span class="header-section-number">2.1</span> The Challenge</h2> |
| | <p>Many GLAM institutions have vast collections of structured documents—index cards, forms, registers—containing valuable information locked in physical or image formats. Manual transcription doesn’t scale, but the structured nature of these documents makes them ideal candidates for AI-powered processing.</p> |
| | <p>Unlocking this data means better discovery, new research possibilities, and integration with modern cataloguing systems.</p> |
| | </section> |
| | <section id="dont-we-just-need-ocr" class="level2" data-number="2.2"> |
| | <h2 data-number="2.2" class="anchored" data-anchor-id="dont-we-just-need-ocr"><span class="header-section-number">2.2</span> Don’t we just need OCR?</h2> |
| | <p>Traditional OCR extracts text from images, but that’s only half the problem. Consider an index card with a name, date, reference number, and description arranged in specific positions. OCR gives you a block of text—but not which part is the name, which is the date, or how they relate.</p> |
| | <p>Often, you don’t even need the raw text—you need the <em>information</em> it contains. A catalogue record doesn’t need “Mr. John Smith, 1847” preserved exactly; it needs <code>name: "John Smith"</code> and <code>year: 1847</code> as usable data.</p> |
| | <p>With OCR alone, you still need someone to parse text into structured fields. For hundreds of documents, that’s manageable. For hundreds of thousands, it’s not.</p> |
| | </section> |
| | <section id="solution-overview" class="level2" data-number="2.3"> |
| | <h2 data-number="2.3" class="anchored" data-anchor-id="solution-overview"><span class="header-section-number">2.3</span> Solution Overview</h2> |
| | <p>Structured extraction is a pattern that works across modalities—text, images, audio transcripts. The core idea is the same: constrain a model to return data in a predefined schema rather than freeform text.</p> |
| | <p>For document images, we use Vision Language Models (VLMs). Unlike OCR, VLMs understand both visual layout and textual content together. They can see that “1847” appears in the date field position, not just that the characters “1847” exist somewhere on the page.</p> |
| | <p>Structured output generation constrains the model to return your fields, your format. The result: input in, structured JSON out.</p> |
| | <p>This section focuses on the image case—extracting from document images—but the same principles apply when working with text or other formats.</p> |
| | <section id="what-this-pattern-looks-like" class="level3" data-number="2.3.1"> |
| | <h3 data-number="2.3.1" class="anchored" data-anchor-id="what-this-pattern-looks-like"><span class="header-section-number">2.3.1</span> What this pattern looks like</h3> |
| | <div class="cell" data-layout-align="default"> |
| | <div class="cell-output-display"> |
| | <div> |
| | <p></p><figure class="figure"><p></p> |
| | <div> |
| | <pre class="mermaid mermaid-js">flowchart LR |
| | A[Document Image] --> B[VLM + Schema] |
| | B --> C[Structured JSON] |
| | C --> D[Catalogue/Database] |
| | </pre> |
| | </div> |
| | <p></p></figure><p></p> |
| | </div> |
| | </div> |
| | </div> |
| | <p>The following chapters walk through this in detail—starting with basic VLM queries, then building to real extraction workflows with evaluation strategies.</p> |
| | </section> |
| | </section> |
| | <section id="when-to-use-this-pattern" class="level2" data-number="2.4"> |
| | <h2 data-number="2.4" class="anchored" data-anchor-id="when-to-use-this-pattern"><span class="header-section-number">2.4</span> When to Use This Pattern</h2> |
| | <p><strong>Good fit:</strong></p> |
| | <ul> |
| | <li>Forms, index cards, registers with consistent layouts</li> |
| | <li>Documents where you know what fields you want to extract</li> |
| | <li>Collections too large for manual transcription</li> |
| | </ul> |
| | <p><strong>Less suited:</strong></p> |
| | <ul> |
| | <li>Free-form manuscripts with no predictable structure</li> |
| | <li>Documents requiring deep contextual interpretation</li> |
| | <li>Cases where verbatim transcription is the goal (use OCR instead)</li> |
| | </ul> |
| |
|
| |
|
| | </section> |
| |
|
| | </main> |
| | <script id="quarto-html-after-body" type="application/javascript"> |
| | window.document.addEventListener("DOMContentLoaded", function (event) { |
| | const icon = ""; |
| | const anchorJS = new window.AnchorJS(); |
| | anchorJS.options = { |
| | placement: 'right', |
| | icon: icon |
| | }; |
| | anchorJS.add('.anchored'); |
| | const isCodeAnnotation = (el) => { |
| | for (const clz of el.classList) { |
| | if (clz.startsWith('code-annotation-')) { |
| | return true; |
| | } |
| | } |
| | return false; |
| | } |
| | const onCopySuccess = function(e) { |
| | |
| | const button = e.trigger; |
| | |
| | button.blur(); |
| | |
| | button.classList.add('code-copy-button-checked'); |
| | var currentTitle = button.getAttribute("title"); |
| | button.setAttribute("title", "Copied!"); |
| | let tooltip; |
| | if (window.bootstrap) { |
| | button.setAttribute("data-bs-toggle", "tooltip"); |
| | button.setAttribute("data-bs-placement", "left"); |
| | button.setAttribute("data-bs-title", "Copied!"); |
| | tooltip = new bootstrap.Tooltip(button, |
| | { trigger: "manual", |
| | customClass: "code-copy-button-tooltip", |
| | offset: [0, -8]}); |
| | tooltip.show(); |
| | } |
| | setTimeout(function() { |
| | if (tooltip) { |
| | tooltip.hide(); |
| | button.removeAttribute("data-bs-title"); |
| | button.removeAttribute("data-bs-toggle"); |
| | button.removeAttribute("data-bs-placement"); |
| | } |
| | button.setAttribute("title", currentTitle); |
| | button.classList.remove('code-copy-button-checked'); |
| | }, 1000); |
| | |
| | e.clearSelection(); |
| | } |
| | const getTextToCopy = function(trigger) { |
| | const outerScaffold = trigger.parentElement.cloneNode(true); |
| | const codeEl = outerScaffold.querySelector('code'); |
| | for (const childEl of codeEl.children) { |
| | if (isCodeAnnotation(childEl)) { |
| | childEl.remove(); |
| | } |
| | } |
| | return codeEl.innerText; |
| | } |
| | const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { |
| | text: getTextToCopy |
| | }); |
| | clipboard.on('success', onCopySuccess); |
| | if (window.document.getElementById('quarto-embedded-source-code-modal')) { |
| | const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { |
| | text: getTextToCopy, |
| | container: window.document.getElementById('quarto-embedded-source-code-modal') |
| | }); |
| | clipboardModal.on('success', onCopySuccess); |
| | } |
| | var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); |
| | var mailtoRegex = new RegExp(/^mailto:/); |
| | var filterRegex = new RegExp('/' + window.location.host + '/'); |
| | var isInternal = (href) => { |
| | return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); |
| | } |
| | |
| | var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); |
| | for (var i=0; i<links.length; i++) { |
| | const link = links[i]; |
| | if (!isInternal(link.href)) { |
| | |
| | |
| | if (link.dataset.originalHref !== undefined) { |
| | link.href = link.dataset.originalHref; |
| | } |
| | } |
| | } |
| | function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { |
| | const config = { |
| | allowHTML: true, |
| | maxWidth: 500, |
| | delay: 100, |
| | arrow: false, |
| | appendTo: function(el) { |
| | return el.parentElement; |
| | }, |
| | interactive: true, |
| | interactiveBorder: 10, |
| | theme: 'quarto', |
| | placement: 'bottom-start', |
| | }; |
| | if (contentFn) { |
| | config.content = contentFn; |
| | } |
| | if (onTriggerFn) { |
| | config.onTrigger = onTriggerFn; |
| | } |
| | if (onUntriggerFn) { |
| | config.onUntrigger = onUntriggerFn; |
| | } |
| | window.tippy(el, config); |
| | } |
| | const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); |
| | for (var i=0; i<noterefs.length; i++) { |
| | const ref = noterefs[i]; |
| | tippyHover(ref, function() { |
| | |
| | let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href'); |
| | try { href = new URL(href).hash; } catch {} |
| | const id = href.replace(/^#\/?/, ""); |
| | const note = window.document.getElementById(id); |
| | if (note) { |
| | return note.innerHTML; |
| | } else { |
| | return ""; |
| | } |
| | }); |
| | } |
| | const xrefs = window.document.querySelectorAll('a.quarto-xref'); |
| | const processXRef = (id, note) => { |
| | |
| | const stripColumnClz = (el) => { |
| | el.classList.remove("page-full", "page-columns"); |
| | if (el.children) { |
| | for (const child of el.children) { |
| | stripColumnClz(child); |
| | } |
| | } |
| | } |
| | stripColumnClz(note) |
| | if (id === null || id.startsWith('sec-')) { |
| | |
| | const container = document.createElement("div"); |
| | if (note.children && note.children.length > 2) { |
| | container.appendChild(note.children[0].cloneNode(true)); |
| | for (let i = 1; i < note.children.length; i++) { |
| | const child = note.children[i]; |
| | if (child.tagName === "P" && child.innerText === "") { |
| | continue; |
| | } else { |
| | container.appendChild(child.cloneNode(true)); |
| | break; |
| | } |
| | } |
| | if (window.Quarto?.typesetMath) { |
| | window.Quarto.typesetMath(container); |
| | } |
| | return container.innerHTML |
| | } else { |
| | if (window.Quarto?.typesetMath) { |
| | window.Quarto.typesetMath(note); |
| | } |
| | return note.innerHTML; |
| | } |
| | } else { |
| | |
| | const anchorLink = note.querySelector('a.anchorjs-link'); |
| | if (anchorLink) { |
| | anchorLink.remove(); |
| | } |
| | if (window.Quarto?.typesetMath) { |
| | window.Quarto.typesetMath(note); |
| | } |
| | if (note.classList.contains("callout")) { |
| | return note.outerHTML; |
| | } else { |
| | return note.innerHTML; |
| | } |
| | } |
| | } |
| | for (var i=0; i<xrefs.length; i++) { |
| | const xref = xrefs[i]; |
| | tippyHover(xref, undefined, function(instance) { |
| | instance.disable(); |
| | let url = xref.getAttribute('href'); |
| | let hash = undefined; |
| | if (url.startsWith('#')) { |
| | hash = url; |
| | } else { |
| | try { hash = new URL(url).hash; } catch {} |
| | } |
| | if (hash) { |
| | const id = hash.replace(/^#\/?/, ""); |
| | const note = window.document.getElementById(id); |
| | if (note !== null) { |
| | try { |
| | const html = processXRef(id, note.cloneNode(true)); |
| | instance.setContent(html); |
| | } finally { |
| | instance.enable(); |
| | instance.show(); |
| | } |
| | } else { |
| | |
| | fetch(url.split('#')[0]) |
| | .then(res => res.text()) |
| | .then(html => { |
| | const parser = new DOMParser(); |
| | const htmlDoc = parser.parseFromString(html, "text/html"); |
| | const note = htmlDoc.getElementById(id); |
| | if (note !== null) { |
| | const html = processXRef(id, note); |
| | instance.setContent(html); |
| | } |
| | }).finally(() => { |
| | instance.enable(); |
| | instance.show(); |
| | }); |
| | } |
| | } else { |
| | |
| | |
| | fetch(url) |
| | .then(res => res.text()) |
| | .then(html => { |
| | const parser = new DOMParser(); |
| | const htmlDoc = parser.parseFromString(html, "text/html"); |
| | const note = htmlDoc.querySelector('main.content'); |
| | if (note !== null) { |
| | |
| | |
| | |
| | if (note.children.length > 0 && note.children[0].tagName === "HEADER") { |
| | note.children[0].remove(); |
| | } |
| | const html = processXRef(null, note); |
| | instance.setContent(html); |
| | } |
| | }).finally(() => { |
| | instance.enable(); |
| | instance.show(); |
| | }); |
| | } |
| | }, function(instance) { |
| | }); |
| | } |
| | let selectedAnnoteEl; |
| | const selectorForAnnotation = ( cell, annotation) => { |
| | let cellAttr = 'data-code-cell="' + cell + '"'; |
| | let lineAttr = 'data-code-annotation="' + annotation + '"'; |
| | const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; |
| | return selector; |
| | } |
| | const selectCodeLines = (annoteEl) => { |
| | const doc = window.document; |
| | const targetCell = annoteEl.getAttribute("data-target-cell"); |
| | const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); |
| | const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); |
| | const lines = annoteSpan.getAttribute("data-code-lines").split(","); |
| | const lineIds = lines.map((line) => { |
| | return targetCell + "-" + line; |
| | }) |
| | let top = null; |
| | let height = null; |
| | let parent = null; |
| | if (lineIds.length > 0) { |
| | |
| | const el = window.document.getElementById(lineIds[0]); |
| | top = el.offsetTop; |
| | height = el.offsetHeight; |
| | parent = el.parentElement.parentElement; |
| | if (lineIds.length > 1) { |
| | const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); |
| | const bottom = lastEl.offsetTop + lastEl.offsetHeight; |
| | height = bottom - top; |
| | } |
| | if (top !== null && height !== null && parent !== null) { |
| | |
| | let div = window.document.getElementById("code-annotation-line-highlight"); |
| | if (div === null) { |
| | div = window.document.createElement("div"); |
| | div.setAttribute("id", "code-annotation-line-highlight"); |
| | div.style.position = 'absolute'; |
| | parent.appendChild(div); |
| | } |
| | div.style.top = top - 2 + "px"; |
| | div.style.height = height + 4 + "px"; |
| | div.style.left = 0; |
| | let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); |
| | if (gutterDiv === null) { |
| | gutterDiv = window.document.createElement("div"); |
| | gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); |
| | gutterDiv.style.position = 'absolute'; |
| | const codeCell = window.document.getElementById(targetCell); |
| | const gutter = codeCell.querySelector('.code-annotation-gutter'); |
| | gutter.appendChild(gutterDiv); |
| | } |
| | gutterDiv.style.top = top - 2 + "px"; |
| | gutterDiv.style.height = height + 4 + "px"; |
| | } |
| | selectedAnnoteEl = annoteEl; |
| | } |
| | }; |
| | const unselectCodeLines = () => { |
| | const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; |
| | elementsIds.forEach((elId) => { |
| | const div = window.document.getElementById(elId); |
| | if (div) { |
| | div.remove(); |
| | } |
| | }); |
| | selectedAnnoteEl = undefined; |
| | }; |
| | |
| | window.addEventListener( |
| | "resize", |
| | throttle(() => { |
| | elRect = undefined; |
| | if (selectedAnnoteEl) { |
| | selectCodeLines(selectedAnnoteEl); |
| | } |
| | }, 10) |
| | ); |
| | function throttle(fn, ms) { |
| | let throttle = false; |
| | let timer; |
| | return (...args) => { |
| | if(!throttle) { |
| | fn.apply(this, args); |
| | throttle = true; |
| | } else { |
| | if(timer) clearTimeout(timer); |
| | timer = setTimeout(() => { |
| | fn.apply(this, args); |
| | timer = throttle = false; |
| | }, ms); |
| | } |
| | }; |
| | } |
| | |
| | const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); |
| | for (const annoteDlNode of annoteDls) { |
| | annoteDlNode.addEventListener('click', (event) => { |
| | const clickedEl = event.target; |
| | if (clickedEl !== selectedAnnoteEl) { |
| | unselectCodeLines(); |
| | const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active'); |
| | if (activeEl) { |
| | activeEl.classList.remove('code-annotation-active'); |
| | } |
| | selectCodeLines(clickedEl); |
| | clickedEl.classList.add('code-annotation-active'); |
| | } else { |
| | |
| | unselectCodeLines(); |
| | clickedEl.classList.remove('code-annotation-active'); |
| | } |
| | }); |
| | } |
| | const findCites = (el) => { |
| | const parentEl = el.parentElement; |
| | if (parentEl) { |
| | const cites = parentEl.dataset.cites; |
| | if (cites) { |
| | return { |
| | el, |
| | cites: cites.split(' ') |
| | }; |
| | } else { |
| | return findCites(el.parentElement) |
| | } |
| | } else { |
| | return undefined; |
| | } |
| | }; |
| | var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]'); |
| | for (var i=0; i<bibliorefs.length; i++) { |
| | const ref = bibliorefs[i]; |
| | const citeInfo = findCites(ref); |
| | if (citeInfo) { |
| | tippyHover(citeInfo.el, function() { |
| | var popup = window.document.createElement('div'); |
| | citeInfo.cites.forEach(function(cite) { |
| | var citeDiv = window.document.createElement('div'); |
| | citeDiv.classList.add('hanging-indent'); |
| | citeDiv.classList.add('csl-entry'); |
| | var biblioDiv = window.document.getElementById('ref-' + cite); |
| | if (biblioDiv) { |
| | citeDiv.innerHTML = biblioDiv.innerHTML; |
| | } |
| | popup.appendChild(citeDiv); |
| | }); |
| | return popup.innerHTML; |
| | }); |
| | } |
| | } |
| | }); |
| | </script> |
| | <nav class="page-navigation"> |
| | <div class="nav-page nav-page-previous"> |
| | <a href="../../patterns/what-is-an-ai-pattern.html" class="pagination-link" aria-label="What is an AI Pattern?"> |
| | <i class="bi bi-arrow-left-short"></i> <span class="nav-page-text"><span class="chapter-number">1</span> <span class="chapter-title">What is an AI Pattern?</span></span> |
| | </a> |
| | </div> |
| | <div class="nav-page nav-page-next"> |
| | <a href="../../patterns/structured-generation/vlm-structured-generation.html" class="pagination-link" aria-label="Structured Information Extraction with Vision Language Models"> |
| | <span class="nav-page-text"><span class="chapter-number">3</span> <span class="chapter-title">Structured Information Extraction with Vision Language Models</span></span> <i class="bi bi-arrow-right-short"></i> |
| | </a> |
| | </div> |
| | </nav> |
| | </div> |
| |
|
| |
|
| |
|
| |
|
| | </body></html> |