> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/logicalstructuretest.md).

# Logical Structure Reader - PDF Sample Code

Sample JavaScript code for using WebViewer, Apryse Web SDK, to explore the logical structure and content of a tagged PDF file, then dumps the information to the console window. In tagged PDF files, St

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="/web/full-api/full-api-overview.md" class="button primary">Full API</a>
{% endhint %}

Sample JavaScript code for using WebViewer to explore the logical structure and content of a tagged PDF file, then dumps the information to the console window. In tagged PDF files, StructTree acts as a central repository for information related to a PDF document's logical structure. The tree consists of StructElement-s and ContentItem-s which are leaf nodes of the structure tree.

Learn more about our full [PDF Data Extraction SDK Capabilities](https://apryse.com/capabilities/extraction).

### **Implementation steps**

Step 1: Follow [get started in your preferred web stack for WebViewer](/web/get-started/readme.md) Step 2: [Enable the full API ](/web/full-api/full-api-overview.md)by passing the `fullAPI` option into the WebViewer constructor Step 3: Add the sample code provided in this guide

This full sample is one of many included in the [manual download of WebViewer.](/web/get-started/manually.md#1-download-webviewer)

<pre class="language-js" data-line-numbers><code class="lang-js">//---------------------------------------------------------------------------------------
// Copyright (c) 2001-2023 by Apryse Software Inc. All Rights Reserved.
// Consult legal.txt regarding legal and license information.
//---------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------
// This sample explores the structure and content of a tagged PDF document and dumps
// the structure information to the console window.
//
// In tagged PDF documents StructTree acts as a central repository for information
// related to a PDF document's logical structure. The tree consists of StructElement-s
// and ContentItem-s which are leaf nodes of the structure tree.
//
// The sample can be extended to access and extract the marked-content elements such
// as text and images.
//---------------------------------------------------------------------------------------

(exports => {
  exports.runLogicalStructureTest = () => {
    const PDFNet = exports.Core.PDFNet;

    const PrintAndIndent = (printState, indent) => {
      if (printState.str) {
        const indentStr = ' '.repeat(printState.indent * 2);
        console.log(indentStr + printState.str);
      }
      printState.str = '';
      printState.indent = indent;
    };

    // Read the structure recursively
    const ReadDocumentStructure = async (element, parent) => {
      if (!(await element.isValid())) {
        return;
      }

      const [type, numKids] = await Promise.all([element.getType(), element.getNumKids()]);

      const elementData = {
        type,
        numKids,
        isLeaf: false,
        children: [],
      };

      if (await element.hasTitle()) {
        elementData.title = await element.getTitle();
      }

      parent.children.push(elementData);

      for (let i = 0; i &#x3C; elementData.numKids; ++i) {
        // Check is the kid is a leaf node (i.e. it is a ContentItem).
        const contentItem = {
          isLeaf: await element.isContentItem(i),
        };
        if (contentItem.isLeaf) {
          const cont = await element.getAsContentItem(i);
          const [type, page] = await Promise.all([cont.getType(), cont.getPage()]);
          const pageNum = await page.getIndex();

          contentItem.type = type;
          contentItem.pageNum = pageNum;

          switch (type) {
            case PDFNet.ContentItem.Type.e_MCID:
            case PDFNet.ContentItem.Type.e_MCR:
              contentItem.mcid = await cont.getMCID();
              break;
            case PDFNet.ContentItem.Type.e_OBJR:
              {
                const refObj = await cont.getRefObj();
                if (refObj) {
                  contentItem.objNum = refObj.getObjNum();
                }
              }
              break;
            default:
              break;
          }
          elementData.children.push(contentItem);
        } else {
          // the kid is another StructElement node.
          await ReadDocumentStructure(await element.getAsStructElem(i), elementData);
        }
      }
    };

    // Read the elements sequentially with a reader
    const ReadElements = async doc => {
      const elements = [];
      const reader = await PDFNet.ElementReader.create();
      for (let itr = await doc.getPageIterator(); await itr.hasNext(); itr.next()) {
        const page = await itr.current();
        reader.beginOnPage(page);
        const pageNum = await page.getIndex();
        let element;
        while ((element = await reader.next())) {
          // Read page contents
          const readElement = {
            type: await element.getType(),
            pageNum,
          };
          if (readElement.type === PDFNet.Element.Type.e_path || readElement.type === PDFNet.Element.Type.e_text || readElement.type === PDFNet.Element.Type.e_path) {
            readElement.text = await element.getTextString();
            // Check if the element is associated with any structural element.
            // Content items are leaf nodes of the structure tree.
            const structParent = await element.getParentStructElement();
            readElement.isValid = await structParent.isValid();
            if (readElement.isValid) {
              readElement.structType = await structParent.getType();
              readElement.mcid = await element.getStructMCID();
              if (await structParent.hasTitle()) {
                readElement.title = await structParent.getTitle();
              }
              readElement.objNum = await (await structParent.getSDFObj()).getObjNum();
            }
            elements.push(readElement);
          }
        }
        reader.end();
      }
      return elements;
    };

    // Used in code snippet 1.
    const ProcessStructElement = (element, indent, printState) => {
      // Print out the type and title info, if any.
      PrintAndIndent(printState, indent++);
      printState.str += `Type: ${element.type}${element.title ? `. Title: ${element.title}` : ''}`;

      for (let i = 0; i &#x3C; element.numKids; ++i) {
        const child = element.children[i];
        // Check is the kid is a leaf node (i.e. it is a ContentItem).
        if (child.isLeaf) {
          PrintAndIndent(printState, indent);
          printState.str += `Content Item. Part of page #${child.pageNum}`;

          PrintAndIndent(printState, indent);
          switch (child.type) {
            case PDFNet.ContentItem.Type.e_MCID:
            case PDFNet.ContentItem.Type.e_MCR:
              printState.str += `MCID: ${child.mcid}`;
              break;
            case PDFNet.ContentItem.Type.e_OBJR:
              printState.str += 'OBJR ';
              if (child.objNum) {
                printState.str += `- Referenced Object#: ${child.objNum}`;
              }
              break;
            default:
              break;
          }
        } else {
          // the kid is another StructElement node.
          ProcessStructElement(child, indent, printState);
        }
      }
    };

    // Used in code snippet 2.
    const ProcessElementsArray = (elementsArray, printState) => {
      for (let i = 0; i &#x3C; elementsArray.length; i++) {
        // Read page contents
        const element = elementsArray[i];
        // In this sample we process only paths &#x26; text, but the code can be
        // extended to handle any element type.
        if (element.type === PDFNet.Element.Type.e_path || element.type === PDFNet.Element.Type.e_text || element.type === PDFNet.Element.Type.e_path) {
          switch (element.type) {
            case PDFNet.Element.Type.e_path: // Process path ...
              printState.str += '\nPATH: ';
              break;
            case PDFNet.Element.Type.e_text: // Process text ...
              printState.str += `\nTEXT: ${element.text}\n`;
              break;
            case PDFNet.Element.Type.e_form: // Process form XObjects
              printState.str += '\nFORM XObject: ';
              // reader.formBegin();
              // await ProcessElements(reader);
              // reader.end();
              break;
          }

          if (element.isValid) {
            // Print out the parent structural element's type, title, and object number.
            printState.str += ` Type: ${element.structType}, MCID: ${element.mcid}`;
            if (element.title) {
              printState.str += `. Title: ${element.title}`;
            }
            printState.str += `, Obj#: ${element.objNum}`;
          }
        }
      }
    };

    // Used in code snippet 3.
    const CreateMCIDDocMap = elementsArray => {
      const mcidDocMap = {};
      for (let i = 0; i &#x3C; elementsArray.length; i++) {
        const element = elementsArray[i];
        if (!mcidDocMap[element.pageNum]) {
          mcidDocMap[element.pageNum] = {};
        }
        const pageMcidMap = mcidDocMap[element.pageNum];
        if (element.mcid >= 0 &#x26;&#x26; element.type === PDFNet.Element.Type.e_text) {
          if (element.mcid in pageMcidMap) {
            pageMcidMap[element.mcid] += element.text;
          } else {
            pageMcidMap[element.mcid] = element.text;
          }
        }
      }
      return mcidDocMap;
    };

    // Used in code snippet 3.
    const ProcessStructElement2 = (element, mcidDocMap, indent, printState) => {
      // Print out the type and title info, if any.
      PrintAndIndent(printState, indent);
      printState.str += `&#x3C;${element.type}${element.title ? ` title="${element.title}"` : ''}>`;

      for (let i = 0; i &#x3C; element.numKids; ++i) {
        const child = element.children[i];
        if (child.isLeaf) {
          if (child.type === PDFNet.ContentItem.Type.e_MCID) {
            const pageNum = child.pageNum;
            const mcidPageMap = mcidDocMap[pageNum];
            if (mcidPageMap) {
              const mcid = child.mcid;
              if (mcid in mcidPageMap) {
                printState.str += mcidPageMap[mcid];
              }
            }
          }
        } else {
          // the kid is another StructElement node.
          ProcessStructElement2(child, mcidDocMap, indent + 1, printState);
        }
      }

      PrintAndIndent(printState, indent);
      printState.str += `&#x3C;/${element.type}>`;
    };

    const main = async () => {
      // Relative path to the folder containing test files.
      const inputPath = '../TestFiles/';
      const printState = { str: '' };
      try {
        // Extract logical structure from a PDF document
        const doc = await PDFNet.PDFDoc.createFromURL(`${inputPath}tagged.pdf`);
        doc.initSecurityHandler();

        const tree = await doc.getStructTree();
        const hasValidTree = await tree.isValid();
        const numKidsFromRoot = await tree.getNumKids();
        const structRoot = {
          children: [],
        };
        let elementsArray = [];

        if (hasValidTree) {
          console.log('Document has a StructTree root.');
          const [, elementsArr] = await Promise.all([
            new Promise(async res => {
              for (let i = 0, numKids = numKidsFromRoot; i &#x3C; numKids; ++i) {
                // Recursively get structure info for all child elements.
                await ReadDocumentStructure(await tree.getKid(i), structRoot);
              }
              res();
            }),
            ReadElements(doc),
          ]);
          elementsArray = elementsArr;
        } else {
          console.log('This document does not contain any logical structure.');
        }

        console.log('____________________________________________________________');
        console.log('Sample 1 - Traverse logical structure tree...');
        for (let i = 0; i &#x3C; structRoot.children.length; ++i) {
          // Recursively get structure info for all child elements.
          ProcessStructElement(structRoot.children[i], 0, printState);
        }
        PrintAndIndent(printState, 0);
        console.log('Done 1.');

        console.log('____________________________________________________________');
        console.log('Sample 2 - Get parent logical structure elements from');
        console.log('layout elements.');
        ProcessElementsArray(elementsArray, printState);
        PrintAndIndent(printState, 0);
        console.log('Done 2.');

        console.log('____________________________________________________________');
        console.log("Sample 3 - 'XML style' extraction of PDF logical structure and page content.");
        {
          const mcidDocMap = CreateMCIDDocMap(elementsArray);
          if (hasValidTree) {
            for (let i = 0, numKids = numKidsFromRoot; i &#x3C; numKids; ++i) {
              ProcessStructElement2(structRoot.children[i], mcidDocMap, 0, printState);
            }
          }
        }
        PrintAndIndent(printState, 0);
        console.log('Done 3.');
        const docBuffer = await doc.saveMemoryBuffer(0);
        saveBufferAsPDFDoc(docBuffer, 'bookmark.pdf');
      } catch (err) {
        console.log(err);
      }
    };

    // add your own license key as the second parameter, e.g. PDFNet.runWithCleanup(main, '<code class="expression">visitor.claims.wvKey || "YOUR_LICENSE_KEY"</code>')
    PDFNet.runWithCleanup(main);
  };
})(window);
// eslint-disable-next-line spaced-comment
//# sourceURL=LogicalStructureTest.js
</code></pre>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/logicalstructuretest.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
