Add FPDF_StructElement_GetParent

Get the parent of the structure element.

Change-Id: I7714fd896a27ea42848d643349e868714afc7da5
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/90433
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfdoc/cpdf_structelement.cpp b/core/fpdfdoc/cpdf_structelement.cpp
index dda016b..c9f1161 100644
--- a/core/fpdfdoc/cpdf_structelement.cpp
+++ b/core/fpdfdoc/cpdf_structelement.cpp
@@ -47,7 +47,13 @@
   LoadKids(m_pDict.Get());
 }
 
-CPDF_StructElement::~CPDF_StructElement() = default;
+CPDF_StructElement::~CPDF_StructElement() {
+  for (auto& kid : m_Kids) {
+    if (kid.m_Type == Kid::kElement) {
+      kid.m_pElement->SetParent(nullptr);
+    }
+  }
+}
 
 ByteString CPDF_StructElement::GetObjType() const {
   return GetDict()->GetStringFor("Type");
diff --git a/core/fpdfdoc/cpdf_structelement.h b/core/fpdfdoc/cpdf_structelement.h
index efeb2dc..c34f000 100644
--- a/core/fpdfdoc/cpdf_structelement.h
+++ b/core/fpdfdoc/cpdf_structelement.h
@@ -35,6 +35,11 @@
   bool UpdateKidIfElement(const CPDF_Dictionary* pDict,
                           CPDF_StructElement* pElement);
 
+  CPDF_StructElement* GetParent() const { return m_pParentElement.Get(); }
+  void SetParent(CPDF_StructElement* pParentElement) {
+    m_pParentElement = pParentElement;
+  }
+
  private:
   struct Kid {
     enum Type { kInvalid, kElement, kPageContent, kStreamContent, kObject };
@@ -60,6 +65,7 @@
 
   UnownedPtr<const CPDF_StructTree> const m_pTree;
   RetainPtr<const CPDF_Dictionary> const m_pDict;
+  UnownedPtr<CPDF_StructElement> m_pParentElement;
   const ByteString m_Type;
   std::vector<Kid> m_Kids;
 };
diff --git a/core/fpdfdoc/cpdf_structtree.cpp b/core/fpdfdoc/cpdf_structtree.cpp
index 6519e1c..133cf5a 100644
--- a/core/fpdfdoc/cpdf_structtree.cpp
+++ b/core/fpdfdoc/cpdf_structtree.cpp
@@ -110,6 +110,8 @@
   if (!pParentElement->UpdateKidIfElement(pDict, pElement.Get()))
     map->erase(pDict);
 
+  pElement->SetParent(pParentElement.Get());
+
   return pElement;
 }
 
diff --git a/fpdfsdk/fpdf_structtree.cpp b/fpdfsdk/fpdf_structtree.cpp
index a8800cd..e429e68 100644
--- a/fpdfsdk/fpdf_structtree.cpp
+++ b/fpdfsdk/fpdf_structtree.cpp
@@ -207,3 +207,14 @@
 
   return FPDFStructElementFromCPDFStructElement(elem->GetKidIfElement(index));
 }
+
+FPDF_EXPORT FPDF_STRUCTELEMENT FPDF_CALLCONV
+FPDF_StructElement_GetParent(FPDF_STRUCTELEMENT struct_element) {
+  CPDF_StructElement* elem =
+      CPDFStructElementFromFPDFStructElement(struct_element);
+  CPDF_StructElement* parent = elem ? elem->GetParent() : nullptr;
+  if (!parent) {
+    return nullptr;
+  }
+  return FPDFStructElementFromCPDFStructElement(parent);
+}
diff --git a/fpdfsdk/fpdf_structtree_embeddertest.cpp b/fpdfsdk/fpdf_structtree_embeddertest.cpp
index bc26022..5ba2bfe 100644
--- a/fpdfsdk/fpdf_structtree_embeddertest.cpp
+++ b/fpdfsdk/fpdf_structtree_embeddertest.cpp
@@ -420,6 +420,38 @@
   UnloadPage(page);
 }
 
+TEST_F(FPDFStructTreeEmbedderTest, GetParent) {
+  ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  {
+    ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page));
+    ASSERT_TRUE(struct_tree);
+    ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
+
+    FPDF_STRUCTELEMENT parent =
+        FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
+    ASSERT_TRUE(parent);
+
+    ASSERT_EQ(1, FPDF_StructElement_CountChildren(parent));
+
+    FPDF_STRUCTELEMENT child = FPDF_StructElement_GetChildAtIndex(parent, 0);
+    ASSERT_TRUE(child);
+
+    // test nullptr inputs
+    ASSERT_EQ(nullptr, FPDF_StructElement_GetParent(nullptr));
+
+    ASSERT_EQ(parent, FPDF_StructElement_GetParent(child));
+
+    // The parent of `parent` is StructTreeRoot and no longer a StructElement.
+    // We currently handle this case by returning a nullptr.
+    ASSERT_EQ(nullptr, FPDF_StructElement_GetParent(parent));
+  }
+
+  UnloadPage(page);
+}
+
 TEST_F(FPDFStructTreeEmbedderTest, GetTitle) {
   ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf"));
   FPDF_PAGE page = LoadPage(0);
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 3d75908..f078104 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -360,6 +360,7 @@
     CHK(FPDF_StructElement_GetLang);
     CHK(FPDF_StructElement_GetMarkedContentID);
     CHK(FPDF_StructElement_GetObjType);
+    CHK(FPDF_StructElement_GetParent);
     CHK(FPDF_StructElement_GetStringAttribute);
     CHK(FPDF_StructElement_GetTitle);
     CHK(FPDF_StructElement_GetType);
diff --git a/public/fpdf_structtree.h b/public/fpdf_structtree.h
index 8daccdb..1485bb0 100644
--- a/public/fpdf_structtree.h
+++ b/public/fpdf_structtree.h
@@ -254,6 +254,19 @@
 FPDF_StructElement_GetChildAtIndex(FPDF_STRUCTELEMENT struct_element,
                                    int index);
 
+// Experimental API.
+// Function: FPDF_StructElement_GetParent
+//          Get the parent of the structure element.
+// Parameters:
+//          struct_tree -   Handle to the struct element.
+// Return value:
+//          The parent structure element or NULL on error.
+// Comments:
+//          If structure element is StructTreeRoot, then this function will
+//          return NULL.
+FPDF_EXPORT FPDF_STRUCTELEMENT FPDF_CALLCONV
+FPDF_StructElement_GetParent(FPDF_STRUCTELEMENT struct_element);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif