Add GetAttribute APIs for structure element

Adding a set of APIs to access "A" attribute map of structure element.

Change-Id: I5b071debdbcb23f9e409eaf7a5d335bc1fa703a1
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/90450
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/cpdfsdk_helpers.h b/fpdfsdk/cpdfsdk_helpers.h
index a037b7a..b585ad1 100644
--- a/fpdfsdk/cpdfsdk_helpers.h
+++ b/fpdfsdk/cpdfsdk_helpers.h
@@ -185,6 +185,15 @@
   return reinterpret_cast<CPDF_StructElement*>(struct_element);
 }
 
+inline FPDF_STRUCTELEMENT_ATTR FPDFStructElementAttrFromCPDFDictionary(
+    const CPDF_Dictionary* dictionary) {
+  return reinterpret_cast<FPDF_STRUCTELEMENT_ATTR>(dictionary);
+}
+inline const CPDF_Dictionary* CPDFDictionaryFromFPDFStructElementAttr(
+    FPDF_STRUCTELEMENT_ATTR struct_element_attr) {
+  return reinterpret_cast<const CPDF_Dictionary*>(struct_element_attr);
+}
+
 inline FPDF_TEXTPAGE FPDFTextPageFromCPDFTextPage(CPDF_TextPage* page) {
   return reinterpret_cast<FPDF_TEXTPAGE>(page);
 }
diff --git a/fpdfsdk/fpdf_structtree.cpp b/fpdfsdk/fpdf_structtree.cpp
index e429e68..538ad2e 100644
--- a/fpdfsdk/fpdf_structtree.cpp
+++ b/fpdfsdk/fpdf_structtree.cpp
@@ -120,6 +120,46 @@
                                              buflen);
 }
 
+FPDF_EXPORT int FPDF_CALLCONV
+FPDF_StructElement_GetAttributeCount(FPDF_STRUCTELEMENT struct_element) {
+  CPDF_StructElement* elem =
+      CPDFStructElementFromFPDFStructElement(struct_element);
+  const CPDF_Dictionary* dict = elem ? elem->GetDict() : nullptr;
+  const CPDF_Object* attr_obj = dict ? dict->GetObjectFor("A") : nullptr;
+  if (!attr_obj)
+    return -1;
+
+  if (attr_obj->IsArray())
+    return attr_obj->AsArray()->size();
+  return attr_obj->IsDictionary() ? 1 : -1;
+}
+
+FPDF_EXPORT FPDF_STRUCTELEMENT_ATTR FPDF_CALLCONV
+FPDF_StructElement_GetAttributeAtIndex(FPDF_STRUCTELEMENT struct_element,
+                                       int index) {
+  CPDF_StructElement* elem =
+      CPDFStructElementFromFPDFStructElement(struct_element);
+  const CPDF_Dictionary* dict = elem ? elem->GetDict() : nullptr;
+  const CPDF_Object* attr_obj = dict ? dict->GetObjectFor("A") : nullptr;
+  if (!attr_obj)
+    return nullptr;
+
+  if (attr_obj->IsDictionary()) {
+    return index == 0 ? FPDFStructElementAttrFromCPDFDictionary(
+                            attr_obj->AsDictionary())
+                      : nullptr;
+  }
+
+  if (attr_obj->IsArray()) {
+    const CPDF_Array* array = attr_obj->AsArray();
+    if (index < 0 || static_cast<size_t>(index) >= array->size())
+      return nullptr;
+    return FPDFStructElementAttrFromCPDFDictionary(array->GetDictAt(index));
+  }
+
+  return nullptr;
+}
+
 FPDF_EXPORT unsigned long FPDF_CALLCONV
 FPDF_StructElement_GetStringAttribute(FPDF_STRUCTELEMENT struct_element,
                                       FPDF_BYTESTRING attr_name,
@@ -218,3 +258,141 @@
   }
   return FPDFStructElementFromCPDFStructElement(parent);
 }
+
+FPDF_EXPORT int FPDF_CALLCONV
+FPDF_StructElement_Attr_GetCount(FPDF_STRUCTELEMENT_ATTR struct_attribute) {
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return -1;
+  return fxcrt::CollectionSize<int>(*dict);
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetName(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                int index,
+                                void* buffer,
+                                unsigned long buflen,
+                                unsigned long* out_buflen) {
+  if (!out_buflen || !buffer)
+    return false;
+
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return false;
+
+  CPDF_DictionaryLocker locker(dict);
+  for (auto& it : locker) {
+    if (index == 0) {
+      *out_buflen =
+          NulTerminateMaybeCopyAndReturnLength(it.first, buffer, buflen);
+      return true;
+    }
+    --index;
+  }
+  return false;
+}
+
+FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV
+FPDF_StructElement_Attr_GetType(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                FPDF_BYTESTRING name) {
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return FPDF_OBJECT_UNKNOWN;
+
+  const CPDF_Object* obj = dict->GetObjectFor(name);
+  return obj ? obj->GetType() : FPDF_OBJECT_UNKNOWN;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_StructElement_Attr_GetBooleanValue(
+    FPDF_STRUCTELEMENT_ATTR struct_attribute,
+    FPDF_BYTESTRING name,
+    FPDF_BOOL* out_value) {
+  if (!out_value)
+    return false;
+
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return false;
+
+  const CPDF_Object* obj = dict->GetObjectFor(name);
+  if (!obj || !obj->IsBoolean())
+    return false;
+
+  *out_value = obj->GetInteger();
+  return true;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetNumberValue(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                       FPDF_BYTESTRING name,
+                                       float* out_value) {
+  if (!out_value)
+    return false;
+
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return false;
+
+  const CPDF_Object* obj = dict->GetObjectFor(name);
+  if (!obj || !obj->IsNumber())
+    return false;
+
+  *out_value = obj->GetNumber();
+  return true;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetStringValue(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                       FPDF_BYTESTRING name,
+                                       void* buffer,
+                                       unsigned long buflen,
+                                       unsigned long* out_buflen) {
+  if (!out_buflen)
+    return false;
+
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return false;
+
+  const CPDF_Object* obj = dict->GetObjectFor(name);
+  if (!obj || !(obj->IsString() || obj->IsName()))
+    return false;
+
+  *out_buflen = Utf16EncodeMaybeCopyAndReturnLength(
+      WideString::FromUTF8(obj->GetString().AsStringView()), buffer, buflen);
+  return true;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetBlobValue(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                     FPDF_BYTESTRING name,
+                                     void* buffer,
+                                     unsigned long buflen,
+                                     unsigned long* out_buflen) {
+  if (!out_buflen)
+    return false;
+
+  const CPDF_Dictionary* dict =
+      CPDFDictionaryFromFPDFStructElementAttr(struct_attribute);
+  if (!dict)
+    return false;
+
+  const CPDF_Object* obj = dict->GetObjectFor(name);
+  if (!obj || !obj->IsString())
+    return false;
+
+  ByteString result = obj->GetString();
+  unsigned long len = result.GetLength();
+
+  if (buffer && len <= buflen)
+    memcpy(buffer, result.c_str(), len);
+
+  *out_buflen = len;
+  return true;
+}
diff --git a/fpdfsdk/fpdf_structtree_embeddertest.cpp b/fpdfsdk/fpdf_structtree_embeddertest.cpp
index 5ba2bfe..1d90b90 100644
--- a/fpdfsdk/fpdf_structtree_embeddertest.cpp
+++ b/fpdfsdk/fpdf_structtree_embeddertest.cpp
@@ -497,6 +497,152 @@
   UnloadPage(page);
 }
 
+TEST_F(FPDFStructTreeEmbedderTest, GetAttributes) {
+  ASSERT_TRUE(OpenDocument("tagged_table.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 document =
+        FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
+    ASSERT_TRUE(document);
+
+    ASSERT_EQ(1, FPDF_StructElement_CountChildren(document));
+    ASSERT_EQ(-1, FPDF_StructElement_GetAttributeCount(document));
+    FPDF_STRUCTELEMENT table = FPDF_StructElement_GetChildAtIndex(document, 0);
+    ASSERT_TRUE(table);
+
+    ASSERT_EQ(2, FPDF_StructElement_CountChildren(table));
+
+    {
+      FPDF_STRUCTELEMENT tr = FPDF_StructElement_GetChildAtIndex(table, 0);
+      ASSERT_TRUE(tr);
+
+      ASSERT_EQ(2, FPDF_StructElement_CountChildren(tr));
+      FPDF_STRUCTELEMENT th = FPDF_StructElement_GetChildAtIndex(tr, 0);
+      ASSERT_TRUE(th);
+
+      ASSERT_EQ(2, FPDF_StructElement_GetAttributeCount(th));
+
+      // nullptr test
+      ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(document, 0));
+      ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(document, -1));
+      ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(th, 2));
+
+      FPDF_STRUCTELEMENT_ATTR attr =
+          FPDF_StructElement_GetAttributeAtIndex(th, 1);
+      ASSERT_TRUE(attr);
+
+      ASSERT_EQ(2, FPDF_StructElement_Attr_GetCount(attr));
+      ASSERT_FALSE(
+          FPDF_StructElement_Attr_GetName(attr, 1, nullptr, 0U, nullptr));
+      char buffer[8] = {};
+      unsigned long out_len = ULONG_MAX;
+      // Deliberately pass in a small buffer size to make sure `buffer` remains
+      // untouched.
+      ASSERT_TRUE(
+          FPDF_StructElement_Attr_GetName(attr, 1, buffer, 1, &out_len));
+      EXPECT_EQ(2U, out_len);
+      for (size_t i = 0; i < pdfium::size(buffer); ++i)
+        EXPECT_EQ(0, buffer[i]);
+
+      ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 1, buffer,
+                                                  sizeof(buffer), &out_len));
+      EXPECT_EQ(2U, out_len);
+      EXPECT_STREQ("O", buffer);
+      EXPECT_EQ(FPDF_OBJECT_NAME,
+                FPDF_StructElement_Attr_GetType(attr, buffer));
+
+      unsigned short str_val[12] = {};
+      ASSERT_TRUE(FPDF_StructElement_Attr_GetStringValue(
+          attr, buffer, str_val, sizeof(str_val), &out_len));
+      EXPECT_EQ(12U, out_len);
+      EXPECT_EQ(L"Table", GetPlatformWString(str_val));
+
+      memset(buffer, 0, sizeof(buffer));
+      ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 0, buffer,
+                                                  sizeof(buffer), &out_len));
+      EXPECT_EQ(8U, out_len);
+      EXPECT_STREQ("ColSpan", buffer);
+      EXPECT_EQ(FPDF_OBJECT_NUMBER,
+                FPDF_StructElement_Attr_GetType(attr, buffer));
+      float num_val;
+      ASSERT_TRUE(
+          FPDF_StructElement_Attr_GetNumberValue(attr, buffer, &num_val));
+      EXPECT_FLOAT_EQ(2.0f, num_val);
+    }
+
+    {
+      FPDF_STRUCTELEMENT tr = FPDF_StructElement_GetChildAtIndex(table, 1);
+      ASSERT_TRUE(tr);
+
+      ASSERT_EQ(1, FPDF_StructElement_GetAttributeCount(tr));
+      // nullptr when index out of range
+      ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(tr, 1));
+
+      ASSERT_EQ(2, FPDF_StructElement_CountChildren(tr));
+      FPDF_STRUCTELEMENT td = FPDF_StructElement_GetChildAtIndex(tr, 1);
+      ASSERT_TRUE(td);
+      {
+        ASSERT_EQ(1, FPDF_StructElement_GetAttributeCount(td));
+        FPDF_STRUCTELEMENT_ATTR attr =
+            FPDF_StructElement_GetAttributeAtIndex(td, 0);
+        ASSERT_TRUE(attr);
+        ASSERT_EQ(3, FPDF_StructElement_Attr_GetCount(attr));
+        // Test string and blob type
+        {
+          char buffer[16] = {};
+          unsigned long out_len = ULONG_MAX;
+          ASSERT_TRUE(FPDF_StructElement_Attr_GetName(
+              attr, 0, buffer, sizeof(buffer), &out_len));
+          EXPECT_EQ(8U, out_len);
+          EXPECT_STREQ("ColProp", buffer);
+
+          EXPECT_EQ(FPDF_OBJECT_STRING,
+                    FPDF_StructElement_Attr_GetType(attr, buffer));
+
+          unsigned short str_val[12] = {};
+          ASSERT_TRUE(FPDF_StructElement_Attr_GetStringValue(
+              attr, buffer, str_val, sizeof(str_val), &out_len));
+          EXPECT_EQ(8U, out_len);
+          EXPECT_EQ(L"Sum", GetPlatformWString(str_val));
+
+          char blob_val[3] = {};
+          ASSERT_TRUE(FPDF_StructElement_Attr_GetBlobValue(
+              attr, buffer, blob_val, sizeof(blob_val), &out_len));
+          EXPECT_EQ(3U, out_len);
+          EXPECT_EQ('S', blob_val[0]);
+          EXPECT_EQ('u', blob_val[1]);
+          EXPECT_EQ('m', blob_val[2]);
+        }
+
+        // Test boolean type
+        {
+          char buffer[16] = {};
+          unsigned long out_len = ULONG_MAX;
+          ASSERT_TRUE(FPDF_StructElement_Attr_GetName(
+              attr, 1, buffer, sizeof(buffer), &out_len));
+          EXPECT_EQ(7U, out_len);
+          EXPECT_STREQ("CurUSD", buffer);
+
+          EXPECT_EQ(FPDF_OBJECT_BOOLEAN,
+                    FPDF_StructElement_Attr_GetType(attr, buffer));
+          FPDF_BOOL val;
+          ASSERT_TRUE(
+              FPDF_StructElement_Attr_GetBooleanValue(attr, buffer, &val));
+          EXPECT_TRUE(val);
+        }
+      }
+    }
+  }
+
+  UnloadPage(page);
+}
+
 TEST_F(FPDFStructTreeEmbedderTest, GetStructTreeForNestedTaggedPDF) {
   ASSERT_TRUE(OpenDocument("tagged_nested.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 f078104..38fc525 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -352,9 +352,18 @@
     CHK(FPDF_GetSignatureObject);
 
     // fpdf_structtree.h
+    CHK(FPDF_StructElement_Attr_GetBlobValue);
+    CHK(FPDF_StructElement_Attr_GetBooleanValue);
+    CHK(FPDF_StructElement_Attr_GetCount);
+    CHK(FPDF_StructElement_Attr_GetName);
+    CHK(FPDF_StructElement_Attr_GetNumberValue);
+    CHK(FPDF_StructElement_Attr_GetStringValue);
+    CHK(FPDF_StructElement_Attr_GetType);
     CHK(FPDF_StructElement_CountChildren);
     CHK(FPDF_StructElement_GetActualText);
     CHK(FPDF_StructElement_GetAltText);
+    CHK(FPDF_StructElement_GetAttributeAtIndex);
+    CHK(FPDF_StructElement_GetAttributeCount);
     CHK(FPDF_StructElement_GetChildAtIndex);
     CHK(FPDF_StructElement_GetID);
     CHK(FPDF_StructElement_GetLang);
diff --git a/public/fpdf_structtree.h b/public/fpdf_structtree.h
index 1485bb0..2de41af 100644
--- a/public/fpdf_structtree.h
+++ b/public/fpdf_structtree.h
@@ -243,8 +243,8 @@
 // Function: FPDF_StructElement_GetChildAtIndex
 //          Get a child in the structure element.
 // Parameters:
-//          struct_tree -   Handle to the struct element.
-//          index       -   The index for the child, 0-based.
+//          struct_element -   Handle to the struct element.
+//          index          -   The index for the child, 0-based.
 // Return value:
 //          The child at the n-th index or NULL on error.
 // Comments:
@@ -258,7 +258,7 @@
 // Function: FPDF_StructElement_GetParent
 //          Get the parent of the structure element.
 // Parameters:
-//          struct_tree -   Handle to the struct element.
+//          struct_element -   Handle to the struct element.
 // Return value:
 //          The parent structure element or NULL on error.
 // Comments:
@@ -267,6 +267,163 @@
 FPDF_EXPORT FPDF_STRUCTELEMENT FPDF_CALLCONV
 FPDF_StructElement_GetParent(FPDF_STRUCTELEMENT struct_element);
 
+// Function: FPDF_StructElement_GetAttributeCount
+//          Count the number of attributes for the structure element.
+// Parameters:
+//          struct_element -   Handle to the struct element.
+// Return value:
+//          The number of attributes, or -1 on error.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDF_StructElement_GetAttributeCount(FPDF_STRUCTELEMENT struct_element);
+
+// Experimental API.
+// Function: FPDF_StructElement_GetAttributeAtIndex
+//          Get an attribute object in the structure element.
+// Parameters:
+//          struct_element -   Handle to the struct element.
+//          index          -   The index for the attribute object, 0-based.
+// Return value:
+//          The attribute object at the n-th index or NULL on error.
+// Comments:
+//          If the attribute object exists but is not a dict, then this
+//          function will return NULL. This will also return NULL for out of
+//          bounds indices.
+FPDF_EXPORT FPDF_STRUCTELEMENT_ATTR FPDF_CALLCONV
+FPDF_StructElement_GetAttributeAtIndex(FPDF_STRUCTELEMENT struct_element, int index);
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetCount
+//          Count the number of attributes in a structure element attribute map.
+// Parameters:
+//          struct_attribute - Handle to the struct element attribute.
+// Return value:
+//          The number of attributes, or -1 on error.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDF_StructElement_Attr_GetCount(FPDF_STRUCTELEMENT_ATTR struct_attribute);
+
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetName
+//          Get the name of an attribute in a structure element attribute map.
+// Parameters:
+//          struct_attribute   - Handle to the struct element attribute.
+//          index              - The index of attribute in the map.
+//          buffer             - A buffer for output. May be NULL. This is only
+//                               modified if |buflen| is longer than the length
+//                               of the key. Optional, pass null to just
+//                               retrieve the size of the buffer needed.
+//          buflen             - The length of the buffer.
+//          out_buflen         - A pointer to variable that will receive the
+//                               minimum buffer size to contain the key. Not
+//                               filled if FALSE is returned.
+// Return value:
+//          TRUE if the operation was successful, FALSE otherwise.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetName(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                int index,
+                                void* buffer,
+                                unsigned long buflen,
+                                unsigned long* out_buflen);
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetType
+//          Get the type of an attribute in a structure element attribute map.
+// Parameters:
+//          struct_attribute   - Handle to the struct element attribute.
+//          name               - The attribute name.
+// Return value:
+//          Returns the type of the value, or FPDF_OBJECT_UNKNOWN in case of
+//          failure.
+FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV
+FPDF_StructElement_Attr_GetType(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                FPDF_BYTESTRING name);
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetBooleanValue
+//          Get the value of a boolean attribute in an attribute map by name as
+//          FPDF_BOOL. FPDF_StructElement_Attr_GetType() should have returned
+//          FPDF_OBJECT_BOOLEAN for this property.
+// Parameters:
+//          struct_attribute   - Handle to the struct element attribute.
+//          name               - The attribute name.
+//          out_value          - A pointer to variable that will receive the
+//                               value. Not filled if false is returned.
+// Return value:
+//          Returns TRUE if the name maps to a boolean value, FALSE otherwise.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetBooleanValue(
+    FPDF_STRUCTELEMENT_ATTR struct_attribute,
+    FPDF_BYTESTRING name,
+    FPDF_BOOL* out_value);
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetNumberValue
+//          Get the value of a number attribute in an attribute map by name as
+//          float. FPDF_StructElement_Attr_GetType() should have returned
+//          FPDF_OBJECT_NUMBER for this property.
+// Parameters:
+//          struct_attribute   - Handle to the struct element attribute.
+//          name               - The attribute name.
+//          out_value          - A pointer to variable that will receive the
+//                               value. Not filled if false is returned.
+// Return value:
+//          Returns TRUE if the name maps to a number value, FALSE otherwise.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetNumberValue(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                       FPDF_BYTESTRING name,
+                                       float* out_value);
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetStringValue
+//          Get the value of a string attribute in an attribute map by name as
+//          string. FPDF_StructElement_Attr_GetType() should have returned
+//          FPDF_OBJECT_STRING or FPDF_OBJECT_NAME for this property.
+// Parameters:
+//          struct_attribute   - Handle to the struct element attribute.
+//          name               - The attribute name.
+//          buffer             - A buffer for holding the returned key in
+//                               UTF-16LE. This is only modified if |buflen| is
+//                               longer than the length of the key. Optional,
+//                               pass null to just retrieve the size of the
+//                               buffer needed.
+//          buflen             - The length of the buffer.
+//          out_buflen         - A pointer to variable that will receive the
+//                               minimum buffer size to contain the key. Not
+//                               filled if FALSE is returned.
+// Return value:
+//          Returns TRUE if the name maps to a string value, FALSE otherwise.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetStringValue(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                       FPDF_BYTESTRING name,
+                                       void* buffer,
+                                       unsigned long buflen,
+                                       unsigned long* out_buflen);
+
+// Experimental API.
+// Function: FPDF_StructElement_Attr_GetBlobValue
+//          Get the value of a blob attribute in an attribute map by name as
+//          string.
+// Parameters:
+//          struct_attribute   - Handle to the struct element attribute.
+//          name               - The attribute name.
+//          buffer             - A buffer for holding the returned value. This
+//                               is only modified if |buflen| is at least as
+//                               long as the length of the value. Optional, pass
+//                               null to just retrieve the size of the buffer
+//                               needed.
+//          buflen             - The length of the buffer.
+//          out_buflen         - A pointer to variable that will receive the
+//                               minimum buffer size to contain the key. Not
+//                               filled if FALSE is returned.
+// Return value:
+//          Returns TRUE if the name maps to a string value, FALSE otherwise.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDF_StructElement_Attr_GetBlobValue(FPDF_STRUCTELEMENT_ATTR struct_attribute,
+                                     FPDF_BYTESTRING name,
+                                     void* buffer,
+                                     unsigned long buflen,
+                                     unsigned long* out_buflen);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
diff --git a/public/fpdfview.h b/public/fpdfview.h
index 779fa5f..a06f8f8 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -75,6 +75,7 @@
 typedef struct fpdf_schhandle_t__* FPDF_SCHHANDLE;
 typedef struct fpdf_signature_t__* FPDF_SIGNATURE;
 typedef struct fpdf_structelement_t__* FPDF_STRUCTELEMENT;
+typedef const struct fpdf_structelement_attr_t__* FPDF_STRUCTELEMENT_ATTR;
 typedef struct fpdf_structtree_t__* FPDF_STRUCTTREE;
 typedef struct fpdf_textpage_t__* FPDF_TEXTPAGE;
 typedef struct fpdf_widget_t__* FPDF_WIDGET;
diff --git a/testing/resources/tagged_table.in b/testing/resources/tagged_table.in
index 70df01c..ee298c5 100644
--- a/testing/resources/tagged_table.in
+++ b/testing/resources/tagged_table.in
@@ -156,6 +156,9 @@
   /K [16 0 R 17 0 R]
   /P 11 0 R
   /Pg 3 0 R
+  /A <<
+      /O /Table
+     >>
   /ID (node14)
 >>
 endobj
@@ -200,6 +203,11 @@
   /S /TD
   /P 13 0 R
   /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /ColProp (Sum)
+        /CurUSD true
+     >>]
   /ID (node18)
 >>
 endobj
diff --git a/testing/resources/tagged_table.pdf b/testing/resources/tagged_table.pdf
index b11437c..4428682 100644
--- a/testing/resources/tagged_table.pdf
+++ b/testing/resources/tagged_table.pdf
@@ -157,6 +157,9 @@
   /K [16 0 R 17 0 R]
   /P 11 0 R
   /Pg 3 0 R
+  /A <<
+      /O /Table
+     >>
   /ID (node14)
 >>
 endobj
@@ -201,6 +204,11 @@
   /S /TD
   /P 13 0 R
   /Pg 3 0 R
+  /A [<<
+        /O /Table
+        /ColProp (Sum)
+        /CurUSD true
+     >>]
   /ID (node18)
 >>
 endobj
@@ -220,14 +228,14 @@
 0000001642 00000 n 
 0000001826 00000 n 
 0000001931 00000 n 
-0000002042 00000 n 
-0000002244 00000 n 
-0000002334 00000 n 
-0000002481 00000 n 
+0000002074 00000 n 
+0000002276 00000 n 
+0000002366 00000 n 
+0000002513 00000 n 
 trailer <<
   /Root 1 0 R
   /Size 18
 >>
 startxref
-2571
+2683
 %%EOF