text: // Copyright 2016 PDFium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
  
  #include "xfa/fxfa/parser/cxfa_node.h"
  
  #include <algorithm>
  #include <map>
  #include <memory>
  #include <set>
  #include <utility>
  #include <vector>
  
  #include "core/fxcrt/autorestorer.h"
  #include "core/fxcrt/cfx_decimal.h"
  #include "core/fxcrt/cfx_memorystream.h"
  #include "core/fxcrt/fx_codepage.h"
  #include "core/fxcrt/fx_extension.h"
  #include "core/fxcrt/xml/cfx_xmlelement.h"
  #include "core/fxcrt/xml/cfx_xmlnode.h"
  #include "core/fxcrt/xml/cfx_xmltext.h"
  #include "core/fxge/dib/cfx_dibitmap.h"
  #include "fxjs/cfxjse_engine.h"
  #include "fxjs/cfxjse_value.h"
  #include "fxjs/xfa/cjx_node.h"
  #include "third_party/base/logging.h"
  #include "third_party/base/ptr_util.h"
  #include "third_party/base/stl_util.h"
  #include "xfa/fde/cfde_textout.h"
  #include "xfa/fgas/font/cfgas_fontmgr.h"
  #include "xfa/fxfa/cxfa_eventparam.h"
  #include "xfa/fxfa/cxfa_ffapp.h"
  #include "xfa/fxfa/cxfa_ffdocview.h"
  #include "xfa/fxfa/cxfa_ffnotify.h"
  #include "xfa/fxfa/cxfa_ffwidget.h"
  #include "xfa/fxfa/cxfa_fontmgr.h"
  #include "xfa/fxfa/cxfa_textprovider.h"
  #include "xfa/fxfa/parser/cxfa_arraynodelist.h"
  #include "xfa/fxfa/parser/cxfa_attachnodelist.h"
  #include "xfa/fxfa/parser/cxfa_bind.h"
  #include "xfa/fxfa/parser/cxfa_border.h"
  #include "xfa/fxfa/parser/cxfa_calculate.h"
  #include "xfa/fxfa/parser/cxfa_caption.h"
  #include "xfa/fxfa/parser/cxfa_comb.h"
  #include "xfa/fxfa/parser/cxfa_decimal.h"
  #include "xfa/fxfa/parser/cxfa_document.h"
  #include "xfa/fxfa/parser/cxfa_event.h"
  #include "xfa/fxfa/parser/cxfa_font.h"
  #include "xfa/fxfa/parser/cxfa_format.h"
  #include "xfa/fxfa/parser/cxfa_image.h"
  #include "xfa/fxfa/parser/cxfa_items.h"
  #include "xfa/fxfa/parser/cxfa_keep.h"
  #include "xfa/fxfa/parser/cxfa_layoutprocessor.h"
  #include "xfa/fxfa/parser/cxfa_localevalue.h"
  #include "xfa/fxfa/parser/cxfa_margin.h"
  #include "xfa/fxfa/parser/cxfa_measurement.h"
  #include "xfa/fxfa/parser/cxfa_nodeiteratortemplate.h"
  #include "xfa/fxfa/parser/cxfa_occur.h"
  #include "xfa/fxfa/parser/cxfa_para.h"
  #include "xfa/fxfa/parser/cxfa_picture.h"
  #include "xfa/fxfa/parser/cxfa_simple_parser.h"
  #include "xfa/fxfa/parser/cxfa_stroke.h"
  #include "xfa/fxfa/parser/cxfa_subform.h"
  #include "xfa/fxfa/parser/cxfa_traversestrategy_xfacontainernode.h"
  #include "xfa/fxfa/parser/cxfa_ui.h"
  #include "xfa/fxfa/parser/cxfa_validate.h"
  #include "xfa/fxfa/parser/cxfa_value.h"
  #include "xfa/fxfa/parser/xfa_basic_data.h"
  #include "xfa/fxfa/parser/xfa_utils.h"
  
  class CXFA_WidgetLayoutData {
   public:
    CXFA_WidgetLayoutData() : m_fWidgetHeight(-1) {}
    virtual ~CXFA_WidgetLayoutData() {}
  
    float m_fWidgetHeight;
  };
  
  namespace {
  
  constexpr uint8_t kMaxExecuteRecursion = 2;
  
  constexpr uint8_t g_inv_base64[128] = {
      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62,  255,
      255, 255, 63,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  255, 255,
      255, 255, 255, 255, 255, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
      10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,
      25,  255, 255, 255, 255, 255, 255, 26,  27,  28,  29,  30,  31,  32,  33,
      34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
      49,  50,  51,  255, 255, 255, 255, 255,
  };
  
  uint8_t* XFA_RemoveBase64Whitespace(const uint8_t* pStr, int32_t iLen) {
    uint8_t* pCP;
    int32_t i = 0, j = 0;
    if (iLen == 0) {
      iLen = strlen((char*)pStr);
    }
    pCP = FX_Alloc(uint8_t, iLen + 1);
    for (; i < iLen; i++) {
      if ((pStr[i] & 128) == 0) {
        if (g_inv_base64[pStr[i]] != 0xFF || pStr[i] == '=') {
          pCP[j++] = pStr[i];
        }
      }
    }
    pCP[j] = '\0';
    return pCP;
  }
  
  int32_t XFA_Base64Decode(const char* pStr, uint8_t* pOutBuffer) {
    if (!pStr) {
      return 0;
    }
    uint8_t* pBuffer =
        XFA_RemoveBase64Whitespace((uint8_t*)pStr, strlen((char*)pStr));
    if (!pBuffer) {
      return 0;
    }
    int32_t iLen = strlen((char*)pBuffer);
    int32_t i = 0, j = 0;
    uint32_t dwLimb = 0;
    for (; i + 3 < iLen; i += 4) {
      if (pBuffer[i] == '=' || pBuffer[i + 1] == '=' || pBuffer[i + 2] == '=' ||
          pBuffer[i + 3] == '=') {
        if (pBuffer[i] == '=' || pBuffer[i + 1] == '=') {
          break;
        }
        if (pBuffer[i + 2] == '=') {
          dwLimb = ((uint32_t)g_inv_base64[pBuffer[i]] << 6) |
                   ((uint32_t)g_inv_base64[pBuffer[i + 1]]);
          pOutBuffer[j] = (uint8_t)(dwLimb >> 4) & 0xFF;
          j++;
        } else {
          dwLimb = ((uint32_t)g_inv_base64[pBuffer[i]] << 12) |
                   ((uint32_t)g_inv_base64[pBuffer[i + 1]] << 6) |
                   ((uint32_t)g_inv_base64[pBuffer[i + 2]]);
          pOutBuffer[j] = (uint8_t)(dwLimb >> 10) & 0xFF;
          pOutBuffer[j + 1] = (uint8_t)(dwLimb >> 2) & 0xFF;
          j += 2;
        }
      } else {
        dwLimb = ((uint32_t)g_inv_base64[pBuffer[i]] << 18) |
                 ((uint32_t)g_inv_base64[pBuffer[i + 1]] << 12) |
                 ((uint32_t)g_inv_base64[pBuffer[i + 2]] << 6) |
                 ((uint32_t)g_inv_base64[pBuffer[i + 3]]);
        pOutBuffer[j] = (uint8_t)(dwLimb >> 16) & 0xff;
        pOutBuffer[j + 1] = (uint8_t)(dwLimb >> 8) & 0xff;
        pOutBuffer[j + 2] = (uint8_t)(dwLimb)&0xff;
        j += 3;
      }
    }
    FX_Free(pBuffer);
    return j;
  }
  
  FXCODEC_IMAGE_TYPE XFA_GetImageType(const WideString& wsType) {
    WideString wsContentType(wsType);
    wsContentType.MakeLower();
    if (wsContentType == L"image/jpg")
      return FXCODEC_IMAGE_JPG;
    if (wsContentType == L"image/png")
      return FXCODEC_IMAGE_PNG;
    if (wsContentType == L"image/gif")
      return FXCODEC_IMAGE_GIF;
    if (wsContentType == L"image/bmp")
      return FXCODEC_IMAGE_BMP;
    if (wsContentType == L"image/tif")
      return FXCODEC_IMAGE_TIF;
    return FXCODEC_IMAGE_UNKNOWN;
  }
  
  RetainPtr<CFX_DIBitmap> XFA_LoadImageData(CXFA_FFDoc* pDoc,
                                            CXFA_Image* pImage,
                                            bool& bNameImage,
                                            int32_t& iImageXDpi,
                                            int32_t& iImageYDpi) {
    WideString wsHref = pImage->GetHref();
    WideString wsImage = pImage->GetContent();
    if (wsHref.IsEmpty() && wsImage.IsEmpty())
      return nullptr;
  
    FXCODEC_IMAGE_TYPE type = XFA_GetImageType(pImage->GetContentType());
    ByteString bsContent;
    uint8_t* pImageBuffer = nullptr;
    RetainPtr<IFX_SeekableReadStream> pImageFileRead;
    if (wsImage.GetLength() > 0) {
      XFA_AttributeEnum iEncoding = pImage->GetTransferEncoding();
      if (iEncoding == XFA_AttributeEnum::Base64) {
        ByteString bsData = wsImage.UTF8Encode();
        int32_t iLength = bsData.GetLength();
        pImageBuffer = FX_Alloc(uint8_t, iLength);
        int32_t iRead = XFA_Base64Decode(bsData.c_str(), pImageBuffer);
        if (iRead > 0) {
          pImageFileRead =
              pdfium::MakeRetain<CFX_MemoryStream>(pImageBuffer, iRead, false);
        }
      } else {
        bsContent = ByteString::FromUnicode(wsImage);
        pImageFileRead = pdfium::MakeRetain<CFX_MemoryStream>(
            const_cast<uint8_t*>(bsContent.raw_str()), bsContent.GetLength(),
            false);
      }
    } else {
      WideString wsURL = wsHref;
      if (wsURL.Left(7) != L"http://" && wsURL.Left(6) != L"ftp://") {
        RetainPtr<CFX_DIBitmap> pBitmap =
            pDoc->GetPDFNamedImage(wsURL.AsStringView(), iImageXDpi, iImageYDpi);
        if (pBitmap) {
          bNameImage = true;
          return pBitmap;
        }
      }
      pImageFileRead = pDoc->GetDocEnvironment()->OpenLinkedFile(pDoc, wsURL);
    }
    if (!pImageFileRead) {
      FX_Free(pImageBuffer);
      return nullptr;
    }
    bNameImage = false;
    RetainPtr<CFX_DIBitmap> pBitmap =
        XFA_LoadImageFromBuffer(pImageFileRead, type, iImageXDpi, iImageYDpi);
    FX_Free(pImageBuffer);
    return pBitmap;
  }
  
  class CXFA_TextLayoutData : public CXFA_WidgetLayoutData {
   public:
    CXFA_TextLayoutData() {}
    ~CXFA_TextLayoutData() override {}
  
    CXFA_TextLayout* GetTextLayout() const { return m_pTextLayout.get(); }
    CXFA_TextProvider* GetTextProvider() const { return m_pTextProvider.get(); }
  
    void LoadText(CXFA_FFDoc* doc, CXFA_Node* pNode) {
      if (m_pTextLayout)
        return;
  
      m_pTextProvider =
          pdfium::MakeUnique<CXFA_TextProvider>(pNode, XFA_TEXTPROVIDERTYPE_Text);
      m_pTextLayout =
          pdfium::MakeUnique<CXFA_TextLayout>(doc, m_pTextProvider.get());
    }
  
   private:
    std::unique_ptr<CXFA_TextLayout> m_pTextLayout;
    std::unique_ptr<CXFA_TextProvider> m_pTextProvider;
  };
  
  class CXFA_ImageLayoutData : public CXFA_WidgetLayoutData {
   public:
    CXFA_ImageLayoutData()
        : m_bNamedImage(false), m_iImageXDpi(0), m_iImageYDpi(0) {}
  
    ~CXFA_ImageLayoutData() override {}
  
    bool LoadImageData(CXFA_FFDoc* doc, CXFA_Node* pNode) {
      if (m_pDIBitmap)
        return true;
  
      CXFA_Value* value = pNode->GetFormValueIfExists();
      if (!value)
        return false;
  
      CXFA_Image* image = value->GetImageIfExists();
      if (!image)
        return false;
  
      pNode->SetImageImage(XFA_LoadImageData(doc, image, m_bNamedImage,
                                             m_iImageXDpi, m_iImageYDpi));
      return !!m_pDIBitmap;
    }
  
    RetainPtr<CFX_DIBitmap> m_pDIBitmap;
    bool m_bNamedImage;
    int32_t m_iImageXDpi;
    int32_t m_iImageYDpi;
  };
  
  class CXFA_FieldLayoutData : public CXFA_WidgetLayoutData {
   public:
    CXFA_FieldLayoutData() {}
    ~CXFA_FieldLayoutData() override {}
  
    bool LoadCaption(CXFA_FFDoc* doc, CXFA_Node* pNode) {
      if (m_pCapTextLayout)
        return true;
      CXFA_Caption* caption = pNode->GetCaptionIfExists();
      if (!caption || caption->IsHidden())
        return false;
  
      m_pCapTextProvider = pdfium::MakeUnique<CXFA_TextProvider>(
          pNode, XFA_TEXTPROVIDERTYPE_Caption);
      m_pCapTextLayout =
          pdfium::MakeUnique<CXFA_TextLayout>(doc, m_pCapTextProvider.get());
      return true;
    }
  
    std::unique_ptr<CXFA_TextLayout> m_pCapTextLayout;
    std::unique_ptr<CXFA_TextProvider> m_pCapTextProvider;
    std::unique_ptr<CFDE_TextOut> m_pTextOut;
    std::vector<float> m_FieldSplitArray;
  };
  
  class CXFA_TextEditData : public CXFA_FieldLayoutData {};
  
  class CXFA_ImageEditData : public CXFA_FieldLayoutData {
   public:
    CXFA_ImageEditData()
        : m_bNamedImage(false), m_iImageXDpi(0), m_iImageYDpi(0) {}
  
    ~CXFA_ImageEditData() override {}
  
    bool LoadImageData(CXFA_FFDoc* doc, CXFA_Node* pNode) {
      if (m_pDIBitmap)
        return true;
  
      CXFA_Value* value = pNode->GetFormValueIfExists();
      if (!value)
        return false;
  
      CXFA_Image* image = value->GetImageIfExists();
      if (!image)
        return false;
  
      pNode->SetImageEditImage(XFA_LoadImageData(doc, image, m_bNamedImage,
                                                 m_iImageXDpi, m_iImageYDpi));
      return !!m_pDIBitmap;
    }
  
    RetainPtr<CFX_DIBitmap> m_pDIBitmap;
    bool m_bNamedImage;
    int32_t m_iImageXDpi;
    int32_t m_iImageYDpi;
  };
  
  bool SplitDateTime(const WideString& wsDateTime,
                     WideString& wsDate,
                     WideString& wsTime) {
    wsDate = L"";
    wsTime = L"";
    if (wsDateTime.IsEmpty())
      return false;
  
    auto nSplitIndex = wsDateTime.Find('T');
    if (!nSplitIndex.has_value())
      nSplitIndex = wsDateTime.Find(' ');
    if (!nSplitIndex.has_value())
      return false;
  
    wsDate = wsDateTime.Left(nSplitIndex.value());
    if (!wsDate.IsEmpty()) {
      if (!std::any_of(wsDate.begin(), wsDate.end(), std::iswdigit))
        return false;
    }
    wsTime = wsDateTime.Right(wsDateTime.GetLength() - nSplitIndex.value() - 1);
    if (!wsTime.IsEmpty()) {
      if (!std::any_of(wsTime.begin(), wsTime.end(), std::iswdigit))
        return false;
    }
    return true;
  }
  
  std::vector<CXFA_Node*> NodesSortedByDocumentIdx(
      const std::set<CXFA_Node*>& rgNodeSet) {
    if (rgNodeSet.empty())
      return std::vector<CXFA_Node*>();
  
    std::vector<CXFA_Node*> rgNodeArray;
    CXFA_Node* pCommonParent = (*rgNodeSet.begin())->GetParent();
    for (CXFA_Node* pNode = pCommonParent->GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pdfium::ContainsValue(rgNodeSet, pNode))
        rgNodeArray.push_back(pNode);
    }
    return rgNodeArray;
  }
  
  using CXFA_NodeSetPair = std::pair<std::set<CXFA_Node*>, std::set<CXFA_Node*>>;
  using CXFA_NodeSetPairMap =
      std::map<uint32_t, std::unique_ptr<CXFA_NodeSetPair>>;
  using CXFA_NodeSetPairMapMap =
      std::map<CXFA_Node*, std::unique_ptr<CXFA_NodeSetPairMap>>;
  
  CXFA_NodeSetPair* NodeSetPairForNode(CXFA_Node* pNode,
                                       CXFA_NodeSetPairMapMap* pMap) {
    CXFA_Node* pParentNode = pNode->GetParent();
    uint32_t dwNameHash = pNode->GetNameHash();
    if (!pParentNode || !dwNameHash)
      return nullptr;
  
    if (!(*pMap)[pParentNode])
      (*pMap)[pParentNode] = pdfium::MakeUnique<CXFA_NodeSetPairMap>();
  
    CXFA_NodeSetPairMap* pNodeSetPairMap = (*pMap)[pParentNode].get();
    if (!(*pNodeSetPairMap)[dwNameHash])
      (*pNodeSetPairMap)[dwNameHash] = pdfium::MakeUnique<CXFA_NodeSetPair>();
  
    return (*pNodeSetPairMap)[dwNameHash].get();
  }
  
  void ReorderDataNodes(const std::set<CXFA_Node*>& sSet1,
                        const std::set<CXFA_Node*>& sSet2,
                        bool bInsertBefore) {
    CXFA_NodeSetPairMapMap rgMap;
    for (CXFA_Node* pNode : sSet1) {
      CXFA_NodeSetPair* pNodeSetPair = NodeSetPairForNode(pNode, &rgMap);
      if (pNodeSetPair)
        pNodeSetPair->first.insert(pNode);
    }
    for (CXFA_Node* pNode : sSet2) {
      CXFA_NodeSetPair* pNodeSetPair = NodeSetPairForNode(pNode, &rgMap);
      if (pNodeSetPair) {
        if (pdfium::ContainsValue(pNodeSetPair->first, pNode))
          pNodeSetPair->first.erase(pNode);
        else
          pNodeSetPair->second.insert(pNode);
      }
    }
    for (const auto& iter1 : rgMap) {
      CXFA_NodeSetPairMap* pNodeSetPairMap = iter1.second.get();
      if (!pNodeSetPairMap)
        continue;
  
      for (const auto& iter2 : *pNodeSetPairMap) {
        CXFA_NodeSetPair* pNodeSetPair = iter2.second.get();
        if (!pNodeSetPair)
          continue;
        if (!pNodeSetPair->first.empty() && !pNodeSetPair->second.empty()) {
          std::vector<CXFA_Node*> rgNodeArray1 =
              NodesSortedByDocumentIdx(pNodeSetPair->first);
          std::vector<CXFA_Node*> rgNodeArray2 =
              NodesSortedByDocumentIdx(pNodeSetPair->second);
          CXFA_Node* pParentNode = nullptr;
          CXFA_Node* pBeforeNode = nullptr;
          if (bInsertBefore) {
            pBeforeNode = rgNodeArray2.front();
            pParentNode = pBeforeNode->GetParent();
          } else {
            CXFA_Node* pLastNode = rgNodeArray2.back();
            pParentNode = pLastNode->GetParent();
            pBeforeNode = pLastNode->GetNextSibling();
          }
          for (auto* pCurNode : rgNodeArray1) {
            pParentNode->RemoveChild(pCurNode, true);
            pParentNode->InsertChild(pCurNode, pBeforeNode);
          }
        }
      }
      pNodeSetPairMap->clear();
    }
  }
  
  float GetEdgeThickness(const std::vector<CXFA_Stroke*>& strokes,
                         bool b3DStyle,
                         int32_t nIndex) {
    float fThickness = 0;
  
    CXFA_Stroke* stroke = strokes[nIndex * 2 + 1];
    if (stroke->IsVisible()) {
      if (nIndex == 0)
        fThickness += 2.5f;
  
      fThickness += stroke->GetThickness() * (b3DStyle ? 4 : 2);
    }
    return fThickness;
  }
  
  }  // namespace
  
  // static
  WideString CXFA_Node::AttributeEnumToName(XFA_AttributeEnum item) {
    return g_XFAEnumData[static_cast<int32_t>(item)].pName;
  }
  
  // static
  Optional<XFA_AttributeEnum> CXFA_Node::NameToAttributeEnum(
      const WideStringView& name) {
    if (name.IsEmpty())
      return {};
  
    auto* it = std::lower_bound(g_XFAEnumData, g_XFAEnumData + g_iXFAEnumCount,
                                FX_HashCode_GetW(name, false),
                                [](const XFA_AttributeEnumInfo& arg,
                                   uint32_t hash) { return arg.uHash < hash; });
    if (it != g_XFAEnumData + g_iXFAEnumCount && name == it->pName)
      return {it->eName};
    return {};
  }
  
  CXFA_Node::CXFA_Node(CXFA_Document* pDoc,
                       XFA_PacketType ePacket,
                       uint32_t validPackets,
                       XFA_ObjectType oType,
                       XFA_Element eType,
                       const PropertyData* properties,
                       const AttributeData* attributes,
                       const WideStringView& elementName,
                       std::unique_ptr<CJX_Object> js_node)
      : CXFA_Object(pDoc, oType, eType, elementName, std::move(js_node)),
        m_Properties(properties),
        m_Attributes(attributes),
        m_ValidPackets(validPackets),
        m_pNext(nullptr),
        m_pChild(nullptr),
        m_pLastChild(nullptr),
        m_pParent(nullptr),
        m_pXMLNode(nullptr),
        m_ePacket(ePacket),
        m_uNodeFlags(XFA_NodeFlag_None),
        m_dwNameHash(0),
        m_pAuxNode(nullptr) {
    ASSERT(m_pDocument);
  }
  
  CXFA_Node::CXFA_Node(CXFA_Document* pDoc,
                       XFA_PacketType ePacket,
                       uint32_t validPackets,
                       XFA_ObjectType oType,
                       XFA_Element eType,
                       const PropertyData* properties,
                       const AttributeData* attributes,
                       const WideStringView& elementName)
      : CXFA_Node(pDoc,
                  ePacket,
                  validPackets,
                  oType,
                  eType,
                  properties,
                  attributes,
                  elementName,
                  pdfium::MakeUnique<CJX_Node>(this)) {}
  
  CXFA_Node::~CXFA_Node() {
    ASSERT(!m_pParent);
  
    CXFA_Node* pNode = m_pChild;
    while (pNode) {
      CXFA_Node* pNext = pNode->m_pNext;
      pNode->m_pParent = nullptr;
      delete pNode;
      pNode = pNext;
    }
    if (m_pXMLNode && IsOwnXMLNode())
      delete m_pXMLNode;
  }
  
  CXFA_Node* CXFA_Node::Clone(bool bRecursive) {
    CXFA_Node* pClone = m_pDocument->CreateNode(m_ePacket, m_elementType);
    if (!pClone)
      return nullptr;
  
    JSObject()->MergeAllData(pClone);
    pClone->UpdateNameHash();
    if (IsNeedSavingXMLNode()) {
      std::unique_ptr<CFX_XMLNode> pCloneXML;
      if (IsAttributeInXML()) {
        WideString wsName = JSObject()
                                ->TryAttribute(XFA_Attribute::Name, false)
                                .value_or(WideString());
        auto pCloneXMLElement = pdfium::MakeUnique<CFX_XMLElement>(wsName);
        WideString wsValue = JSObject()->GetCData(XFA_Attribute::Value);
        if (!wsValue.IsEmpty())
          pCloneXMLElement->SetTextData(WideString(wsValue));
  
        pCloneXML.reset(pCloneXMLElement.release());
        pClone->JSObject()->SetEnum(XFA_Attribute::Contains,
                                    XFA_AttributeEnum::Unknown, false);
      } else {
        pCloneXML = m_pXMLNode->Clone();
      }
      pClone->SetXMLMappingNode(pCloneXML.release());
      pClone->SetFlag(XFA_NodeFlag_OwnXMLNode, false);
    }
    if (bRecursive) {
      for (CXFA_Node* pChild = GetFirstChild(); pChild;
           pChild = pChild->GetNextSibling()) {
        pClone->InsertChild(pChild->Clone(bRecursive), nullptr);
      }
    }
    pClone->SetFlag(XFA_NodeFlag_Initialized, true);
    pClone->SetBindingNode(nullptr);
    return pClone;
  }
  
  CXFA_Node* CXFA_Node::GetPrevSibling() const {
    if (!m_pParent || m_pParent->m_pChild == this)
      return nullptr;
  
    for (CXFA_Node* pNode = m_pParent->m_pChild; pNode; pNode = pNode->m_pNext) {
      if (pNode->m_pNext == this)
        return pNode;
    }
    return nullptr;
  }
  
  CXFA_Node* CXFA_Node::GetNextContainerSibling() const {
    CXFA_Node* pNode = m_pNext;
    while (pNode && pNode->GetObjectType() != XFA_ObjectType::ContainerNode)
      pNode = pNode->m_pNext;
    return pNode;
  }
  
  CXFA_Node* CXFA_Node::GetPrevContainerSibling() const {
    if (!m_pParent || m_pParent->m_pChild == this)
      return nullptr;
  
    CXFA_Node* container = nullptr;
    for (CXFA_Node* pNode = m_pParent->m_pChild; pNode; pNode = pNode->m_pNext) {
      if (pNode->GetObjectType() == XFA_ObjectType::ContainerNode)
        container = pNode;
      if (pNode->m_pNext == this)
        return container;
    }
    return nullptr;
  }
  
  CXFA_Node* CXFA_Node::GetFirstContainerChild() const {
    CXFA_Node* pNode = m_pChild;
    while (pNode && pNode->GetObjectType() != XFA_ObjectType::ContainerNode)
      pNode = pNode->m_pNext;
    return pNode;
  }
  
  CXFA_Node* CXFA_Node::GetContainerParent() const {
    CXFA_Node* pNode = m_pParent;
    while (pNode && pNode->GetObjectType() != XFA_ObjectType::ContainerNode)
      pNode = pNode->m_pParent;
    return pNode;
  }
  
  bool CXFA_Node::IsValidInPacket(XFA_PacketType packet) const {
    return !!(m_ValidPackets & (1 << static_cast<uint8_t>(packet)));
  }
  
  const CXFA_Node::PropertyData* CXFA_Node::GetPropertyData(
      XFA_Element property) const {
    if (m_Properties == nullptr)
      return nullptr;
  
    for (size_t i = 0;; ++i) {
      const PropertyData* data = m_Properties + i;
      if (data->property == XFA_Element::Unknown)
        break;
      if (data->property == property)
        return data;
    }
    return nullptr;
  }
  
  bool CXFA_Node::HasProperty(XFA_Element property) const {
    return !!GetPropertyData(property);
  }
  
  bool CXFA_Node::HasPropertyFlags(XFA_Element property, uint8_t flags) const {
    const PropertyData* data = GetPropertyData(property);
    return data && !!(data->flags & flags);
  }
  
  uint8_t CXFA_Node::PropertyOccuranceCount(XFA_Element property) const {
    const PropertyData* data = GetPropertyData(property);
    return data ? data->occurance_count : 0;
  }
  
  Optional<XFA_Element> CXFA_Node::GetFirstPropertyWithFlag(uint8_t flag) {
    if (m_Properties == nullptr)
      return {};
  
    for (size_t i = 0;; ++i) {
      const PropertyData* data = m_Properties + i;
      if (data->property == XFA_Element::Unknown)
        break;
      if (data->flags & flag)
        return {data->property};
    }
    return {};
  }
  
  const CXFA_Node::AttributeData* CXFA_Node::GetAttributeData(
      XFA_Attribute attr) const {
    if (m_Attributes == nullptr)
      return nullptr;
  
    for (size_t i = 0;; ++i) {
      const AttributeData* cur_attr = &m_Attributes[i];
      if (cur_attr->attribute == XFA_Attribute::Unknown)
        break;
      if (cur_attr->attribute == attr)
        return cur_attr;
    }
    return nullptr;
  }
  
  bool CXFA_Node::HasAttribute(XFA_Attribute attr) const {
    return !!GetAttributeData(attr);
  }
  
  // Note: This Method assumes that i is a valid index ....
  XFA_Attribute CXFA_Node::GetAttribute(size_t i) const {
    if (m_Attributes == nullptr)
      return XFA_Attribute::Unknown;
    return m_Attributes[i].attribute;
  }
  
  XFA_AttributeType CXFA_Node::GetAttributeType(XFA_Attribute type) const {
    const AttributeData* data = GetAttributeData(type);
    return data ? data->type : XFA_AttributeType::CData;
  }
  
  std::vector<CXFA_Node*> CXFA_Node::GetNodeList(uint32_t dwTypeFilter,
                                                 XFA_Element eTypeFilter) {
    if (eTypeFilter != XFA_Element::Unknown) {
      std::vector<CXFA_Node*> nodes;
      for (CXFA_Node* pChild = m_pChild; pChild; pChild = pChild->m_pNext) {
        if (pChild->GetElementType() == eTypeFilter)
          nodes.push_back(pChild);
      }
      return nodes;
    }
  
    if (dwTypeFilter == (XFA_NODEFILTER_Children | XFA_NODEFILTER_Properties)) {
      std::vector<CXFA_Node*> nodes;
      for (CXFA_Node* pChild = m_pChild; pChild; pChild = pChild->m_pNext)
        nodes.push_back(pChild);
      return nodes;
    }
  
    if (dwTypeFilter == 0)
      return std::vector<CXFA_Node*>();
  
    bool bFilterChildren = !!(dwTypeFilter & XFA_NODEFILTER_Children);
    bool bFilterProperties = !!(dwTypeFilter & XFA_NODEFILTER_Properties);
    bool bFilterOneOfProperties = !!(dwTypeFilter & XFA_NODEFILTER_OneOfProperty);
    std::vector<CXFA_Node*> nodes;
    for (CXFA_Node* pChild = m_pChild; pChild; pChild = pChild->m_pNext) {
      if (!HasProperty(pChild->GetElementType())) {
        if (bFilterProperties) {
          nodes.push_back(pChild);
        } else if (bFilterOneOfProperties &&
                   HasPropertyFlags(pChild->GetElementType(),
                                    XFA_PROPERTYFLAG_OneOf)) {
          nodes.push_back(pChild);
        } else if (bFilterChildren &&
                   (pChild->GetElementType() == XFA_Element::Variables ||
                    pChild->GetElementType() == XFA_Element::PageSet)) {
          nodes.push_back(pChild);
        }
      } else if (bFilterChildren) {
        nodes.push_back(pChild);
      }
    }
  
    if (!bFilterOneOfProperties || !nodes.empty())
      return nodes;
    if (m_Properties == nullptr)
      return nodes;
  
    Optional<XFA_Element> property =
        GetFirstPropertyWithFlag(XFA_PROPERTYFLAG_DefaultOneOf);
    if (!property)
      return nodes;
  
    CXFA_Node* pNewNode = m_pDocument->CreateNode(GetPacketType(), *property);
    if (pNewNode) {
      InsertChild(pNewNode, nullptr);
      pNewNode->SetFlag(XFA_NodeFlag_Initialized, true);
      nodes.push_back(pNewNode);
    }
    return nodes;
  }
  
  CXFA_Node* CXFA_Node::CreateSamePacketNode(XFA_Element eType) {
    CXFA_Node* pNode = m_pDocument->CreateNode(m_ePacket, eType);
    pNode->SetFlag(XFA_NodeFlag_Initialized, true);
    return pNode;
  }
  
  CXFA_Node* CXFA_Node::CloneTemplateToForm(bool bRecursive) {
    ASSERT(m_ePacket == XFA_PacketType::Template);
    CXFA_Node* pClone =
        m_pDocument->CreateNode(XFA_PacketType::Form, m_elementType);
    if (!pClone)
      return nullptr;
  
    pClone->SetTemplateNode(this);
    pClone->UpdateNameHash();
    pClone->SetXMLMappingNode(GetXMLMappingNode());
    if (bRecursive) {
      for (CXFA_Node* pChild = GetFirstChild(); pChild;
           pChild = pChild->GetNextSibling()) {
        pClone->InsertChild(pChild->CloneTemplateToForm(bRecursive), nullptr);
      }
    }
    pClone->SetFlag(XFA_NodeFlag_Initialized, true);
    return pClone;
  }
  
  CXFA_Node* CXFA_Node::GetTemplateNodeIfExists() const {
    return m_pAuxNode;
  }
  
  void CXFA_Node::SetTemplateNode(CXFA_Node* pTemplateNode) {
    m_pAuxNode = pTemplateNode;
  }
  
  CXFA_Node* CXFA_Node::GetBindData() {
    ASSERT(GetPacketType() == XFA_PacketType::Form);
    return GetBindingNode();
  }
  
  std::vector<UnownedPtr<CXFA_Node>>* CXFA_Node::GetBindItems() {
    return GetBindingNodes();
  }
  
  int32_t CXFA_Node::AddBindItem(CXFA_Node* pFormNode) {
    ASSERT(pFormNode);
  
    if (BindsFormItems()) {
      bool found = false;
      for (auto& v : binding_nodes_) {
        if (v.Get() == pFormNode) {
          found = true;
          break;
        }
      }
      if (!found)
        binding_nodes_.emplace_back(pFormNode);
      return pdfium::CollectionSize<int32_t>(binding_nodes_);
    }
  
    CXFA_Node* pOldFormItem = GetBindingNode();
    if (!pOldFormItem) {
      SetBindingNode(pFormNode);
      return 1;
    }
    if (pOldFormItem == pFormNode)
      return 1;
  
    std::vector<UnownedPtr<CXFA_Node>> items;
    items.emplace_back(pOldFormItem);
    items.emplace_back(pFormNode);
    SetBindingNodes(std::move(items));
  
    m_uNodeFlags |= XFA_NodeFlag_BindFormItems;
    return 2;
  }
  
  int32_t CXFA_Node::RemoveBindItem(CXFA_Node* pFormNode) {
    if (BindsFormItems()) {
      auto it = std::find_if(binding_nodes_.begin(), binding_nodes_.end(),
                             [&pFormNode](const UnownedPtr<CXFA_Node>& node) {
                               return node.Get() == pFormNode;
                             });
      if (it != binding_nodes_.end())
        binding_nodes_.erase(it);
  
      if (binding_nodes_.size() == 1) {
        m_uNodeFlags &= ~XFA_NodeFlag_BindFormItems;
        return 1;
      }
      return pdfium::CollectionSize<int32_t>(binding_nodes_);
    }
  
    CXFA_Node* pOldFormItem = GetBindingNode();
    if (pOldFormItem != pFormNode)
      return pOldFormItem ? 1 : 0;
  
    SetBindingNode(nullptr);
    return 0;
  }
  
  bool CXFA_Node::HasBindItem() {
    return GetPacketType() == XFA_PacketType::Datasets && GetBindingNode();
  }
  
  CXFA_Node* CXFA_Node::GetContainerNode() {
    if (GetPacketType() != XFA_PacketType::Form)
      return nullptr;
    XFA_Element eType = GetElementType();
    if (eType == XFA_Element::ExclGroup)
      return nullptr;
    CXFA_Node* pParentNode = GetParent();
    if (pParentNode && pParentNode->GetElementType() == XFA_Element::ExclGroup)
      return nullptr;
  
    if (eType == XFA_Element::Field) {
      if (IsChoiceListMultiSelect())
        return nullptr;
  
      WideString wsPicture = GetPictureContent(XFA_VALUEPICTURE_DataBind);
      if (!wsPicture.IsEmpty())
        return this;
  
      CXFA_Node* pDataNode = GetBindData();
      if (!pDataNode)
        return nullptr;
  
      CXFA_Node* pFieldNode = nullptr;
      for (const auto& pFormNode : *(pDataNode->GetBindItems())) {
        if (!pFormNode || pFormNode->HasRemovedChildren())
          continue;
        pFieldNode = pFormNode->IsWidgetReady() ? pFormNode.Get() : nullptr;
        if (pFieldNode)
          wsPicture = pFieldNode->GetPictureContent(XFA_VALUEPICTURE_DataBind);
        if (!wsPicture.IsEmpty())
          break;
  
        pFieldNode = nullptr;
      }
      return pFieldNode;
    }
  
    CXFA_Node* pGrandNode = pParentNode ? pParentNode->GetParent() : nullptr;
    CXFA_Node* pValueNode =
        (pParentNode && pParentNode->GetElementType() == XFA_Element::Value)
            ? pParentNode
            : nullptr;
    if (!pValueNode) {
      pValueNode =
          (pGrandNode && pGrandNode->GetElementType() == XFA_Element::Value)
              ? pGrandNode
              : nullptr;
    }
    CXFA_Node* pParentOfValueNode =
        pValueNode ? pValueNode->GetParent() : nullptr;
    return pParentOfValueNode ? pParentOfValueNode->GetContainerNode() : nullptr;
  }
  
  IFX_Locale* CXFA_Node::GetLocale() {
    Optional<WideString> localeName = GetLocaleName();
    if (!localeName)
      return nullptr;
    if (localeName.value() == L"ambient")
      return GetDocument()->GetLocalMgr()->GetDefLocale();
    return GetDocument()->GetLocalMgr()->GetLocaleByName(localeName.value());
  }
  
  Optional<WideString> CXFA_Node::GetLocaleName() {
    CXFA_Node* pForm = GetDocument()->GetXFAObject(XFA_HASHCODE_Form)->AsNode();
    CXFA_Subform* pTopSubform =
        pForm->GetFirstChildByClass<CXFA_Subform>(XFA_Element::Subform);
    ASSERT(pTopSubform);
  
    CXFA_Node* pLocaleNode = this;
    do {
      Optional<WideString> localeName =
          pLocaleNode->JSObject()->TryCData(XFA_Attribute::Locale, false);
      if (localeName)
        return localeName;
  
      pLocaleNode = pLocaleNode->GetParent();
    } while (pLocaleNode && pLocaleNode != pTopSubform);
  
    CXFA_Node* pConfig = ToNode(GetDocument()->GetXFAObject(XFA_HASHCODE_Config));
    Optional<WideString> localeName = {
        WideString(GetDocument()->GetLocalMgr()->GetConfigLocaleName(pConfig))};
    if (localeName && !localeName->IsEmpty())
      return localeName;
  
    if (pTopSubform) {
      localeName =
          pTopSubform->JSObject()->TryCData(XFA_Attribute::Locale, false);
      if (localeName)
        return localeName;
    }
  
    IFX_Locale* pLocale = GetDocument()->GetLocalMgr()->GetDefLocale();
    if (!pLocale)
      return {};
  
    return {pLocale->GetName()};
  }
  
  XFA_AttributeEnum CXFA_Node::GetIntact() {
    CXFA_Keep* pKeep = GetFirstChildByClass<CXFA_Keep>(XFA_Element::Keep);
    XFA_AttributeEnum eLayoutType = JSObject()
                                        ->TryEnum(XFA_Attribute::Layout, true)
                                        .value_or(XFA_AttributeEnum::Position);
    if (pKeep) {
      Optional<XFA_AttributeEnum> intact =
          pKeep->JSObject()->TryEnum(XFA_Attribute::Intact, false);
      if (intact) {
        if (*intact == XFA_AttributeEnum::None &&
            eLayoutType == XFA_AttributeEnum::Row &&
            m_pDocument->GetCurVersionMode() < XFA_VERSION_208) {
          CXFA_Node* pPreviewRow = GetPrevContainerSibling();
          if (pPreviewRow &&
              pPreviewRow->JSObject()->GetEnum(XFA_Attribute::Layout) ==
                  XFA_AttributeEnum::Row) {
            Optional<XFA_AttributeEnum> value =
                pKeep->JSObject()->TryEnum(XFA_Attribute::Previous, false);
            if (value && (*value == XFA_AttributeEnum::ContentArea ||
                          *value == XFA_AttributeEnum::PageArea)) {
              return XFA_AttributeEnum::ContentArea;
            }
  
            CXFA_Keep* pNode =
                pPreviewRow->GetFirstChildByClass<CXFA_Keep>(XFA_Element::Keep);
            Optional<XFA_AttributeEnum> ret;
            if (pNode)
              ret = pNode->JSObject()->TryEnum(XFA_Attribute::Next, false);
            if (ret && (*ret == XFA_AttributeEnum::ContentArea ||
                        *ret == XFA_AttributeEnum::PageArea)) {
              return XFA_AttributeEnum::ContentArea;
            }
          }
        }
        return *intact;
      }
    }
  
    switch (GetElementType()) {
      case XFA_Element::Subform:
        switch (eLayoutType) {
          case XFA_AttributeEnum::Position:
          case XFA_AttributeEnum::Row:
            return XFA_AttributeEnum::ContentArea;
          default:
            return XFA_AttributeEnum::None;
        }
      case XFA_Element::Field: {
        CXFA_Node* parent = GetParent();
        if (!parent || parent->GetElementType() == XFA_Element::PageArea)
          return XFA_AttributeEnum::ContentArea;
        if (parent->GetIntact() != XFA_AttributeEnum::None)
          return XFA_AttributeEnum::ContentArea;
  
        XFA_AttributeEnum eParLayout = parent->JSObject()
                                           ->TryEnum(XFA_Attribute::Layout, true)
                                           .value_or(XFA_AttributeEnum::Position);
        if (eParLayout == XFA_AttributeEnum::Position ||
            eParLayout == XFA_AttributeEnum::Row ||
            eParLayout == XFA_AttributeEnum::Table) {
          return XFA_AttributeEnum::None;
        }
  
        XFA_VERSION version = m_pDocument->GetCurVersionMode();
        if (eParLayout == XFA_AttributeEnum::Tb && version < XFA_VERSION_208) {
          Optional<CXFA_Measurement> measureH =
              JSObject()->TryMeasure(XFA_Attribute::H, false);
          if (measureH)
            return XFA_AttributeEnum::ContentArea;
        }
        return XFA_AttributeEnum::None;
      }
      case XFA_Element::Draw:
        return XFA_AttributeEnum::ContentArea;
      default:
        return XFA_AttributeEnum::None;
    }
  }
  
  CXFA_Node* CXFA_Node::GetDataDescriptionNode() {
    if (m_ePacket == XFA_PacketType::Datasets)
      return m_pAuxNode;
    return nullptr;
  }
  
  void CXFA_Node::SetDataDescriptionNode(CXFA_Node* pDataDescriptionNode) {
    ASSERT(m_ePacket == XFA_PacketType::Datasets);
    m_pAuxNode = pDataDescriptionNode;
  }
  
  CXFA_Node* CXFA_Node::GetModelNode() {
    switch (GetPacketType()) {
      case XFA_PacketType::Xdp:
        return m_pDocument->GetRoot();
      case XFA_PacketType::Config:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_Config));
      case XFA_PacketType::Template:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_Template));
      case XFA_PacketType::Form:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_Form));
      case XFA_PacketType::Datasets:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_Datasets));
      case XFA_PacketType::LocaleSet:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_LocaleSet));
      case XFA_PacketType::ConnectionSet:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_ConnectionSet));
      case XFA_PacketType::SourceSet:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_SourceSet));
      case XFA_PacketType::Xdc:
        return ToNode(m_pDocument->GetXFAObject(XFA_HASHCODE_Xdc));
      default:
        return this;
    }
  }
  
  size_t CXFA_Node::CountChildren(XFA_Element eType, bool bOnlyChild) {
    size_t count = 0;
    for (CXFA_Node* pNode = m_pChild; pNode; pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() != eType && eType != XFA_Element::Unknown)
        continue;
      if (bOnlyChild && HasProperty(pNode->GetElementType()))
        continue;
      ++count;
    }
    return count;
  }
  
  CXFA_Node* CXFA_Node::GetChildInternal(size_t index,
                                         XFA_Element eType,
                                         bool bOnlyChild) {
    size_t count = 0;
    for (CXFA_Node* pNode = m_pChild; pNode; pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() != eType && eType != XFA_Element::Unknown)
        continue;
      if (bOnlyChild && HasProperty(pNode->GetElementType()))
        continue;
      if (count == index)
        return pNode;
  
      ++count;
    }
    return nullptr;
  }
  
  int32_t CXFA_Node::InsertChild(int32_t index, CXFA_Node* pNode) {
    ASSERT(!pNode->m_pNext);
    pNode->m_pParent = this;
    bool ret = m_pDocument->RemovePurgeNode(pNode);
    ASSERT(ret);
    (void)ret;  // Avoid unused variable warning.
  
    if (!m_pChild || index == 0) {
      if (index > 0) {
        return -1;
      }
      pNode->m_pNext = m_pChild;
      m_pChild = pNode;
      index = 0;
    } else if (index < 0) {
      m_pLastChild->m_pNext = pNode;
    } else {
      CXFA_Node* pPrev = m_pChild;
      int32_t iCount = 0;
      while (++iCount != index && pPrev->m_pNext) {
        pPrev = pPrev->m_pNext;
      }
      if (index > 0 && index != iCount) {
        return -1;
      }
      pNode->m_pNext = pPrev->m_pNext;
      pPrev->m_pNext = pNode;
      index = iCount;
    }
    if (!pNode->m_pNext) {
      m_pLastChild = pNode;
    }
    ASSERT(m_pLastChild);
    ASSERT(!m_pLastChild->m_pNext);
    pNode->ClearFlag(XFA_NodeFlag_HasRemovedChildren);
    CXFA_FFNotify* pNotify = m_pDocument->GetNotify();
    if (pNotify)
      pNotify->OnChildAdded(this);
  
    if (IsNeedSavingXMLNode() && pNode->m_pXMLNode) {
      ASSERT(!pNode->m_pXMLNode->GetNodeItem(CFX_XMLNode::Parent));
      m_pXMLNode->InsertChildNode(pNode->m_pXMLNode, index);
      pNode->ClearFlag(XFA_NodeFlag_OwnXMLNode);
    }
    return index;
  }
  
  bool CXFA_Node::InsertChild(CXFA_Node* pNode, CXFA_Node* pBeforeNode) {
    if (!pNode || pNode->m_pParent ||
        (pBeforeNode && pBeforeNode->m_pParent != this)) {
      NOTREACHED();
      return false;
    }
    bool ret = m_pDocument->RemovePurgeNode(pNode);
    ASSERT(ret);
    (void)ret;  // Avoid unused variable warning.
  
    int32_t nIndex = -1;
    pNode->m_pParent = this;
    if (!m_pChild || pBeforeNode == m_pChild) {
      pNode->m_pNext = m_pChild;
      m_pChild = pNode;
      nIndex = 0;
    } else if (!pBeforeNode) {
      pNode->m_pNext = m_pLastChild->m_pNext;
      m_pLastChild->m_pNext = pNode;
    } else {
      nIndex = 1;
      CXFA_Node* pPrev = m_pChild;
      while (pPrev->m_pNext != pBeforeNode) {
        pPrev = pPrev->m_pNext;
        nIndex++;
      }
      pNode->m_pNext = pPrev->m_pNext;
      pPrev->m_pNext = pNode;
    }
    if (!pNode->m_pNext) {
      m_pLastChild = pNode;
    }
    ASSERT(m_pLastChild);
    ASSERT(!m_pLastChild->m_pNext);
    pNode->ClearFlag(XFA_NodeFlag_HasRemovedChildren);
    CXFA_FFNotify* pNotify = m_pDocument->GetNotify();
    if (pNotify)
      pNotify->OnChildAdded(this);
  
    if (IsNeedSavingXMLNode() && pNode->m_pXMLNode) {
      ASSERT(!pNode->m_pXMLNode->GetNodeItem(CFX_XMLNode::Parent));
      m_pXMLNode->InsertChildNode(pNode->m_pXMLNode, nIndex);
      pNode->ClearFlag(XFA_NodeFlag_OwnXMLNode);
    }
    return true;
  }
  
  CXFA_Node* CXFA_Node::Deprecated_GetPrevSibling() {
    if (!m_pParent) {
      return nullptr;
    }
    for (CXFA_Node* pSibling = m_pParent->m_pChild; pSibling;
         pSibling = pSibling->m_pNext) {
      if (pSibling->m_pNext == this) {
        return pSibling;
      }
    }
    return nullptr;
  }
  
  bool CXFA_Node::RemoveChild(CXFA_Node* pNode, bool bNotify) {
    if (!pNode || pNode->m_pParent != this) {
      NOTREACHED();
      return false;
    }
    if (m_pChild == pNode) {
      m_pChild = pNode->m_pNext;
      if (m_pLastChild == pNode) {
        m_pLastChild = pNode->m_pNext;
      }
      pNode->m_pNext = nullptr;
      pNode->m_pParent = nullptr;
    } else {
      CXFA_Node* pPrev = pNode->Deprecated_GetPrevSibling();
      pPrev->m_pNext = pNode->m_pNext;
      if (m_pLastChild == pNode) {
        m_pLastChild = pNode->m_pNext ? pNode->m_pNext : pPrev;
      }
      pNode->m_pNext = nullptr;
      pNode->m_pParent = nullptr;
    }
    ASSERT(!m_pLastChild || !m_pLastChild->m_pNext);
    OnRemoved(bNotify);
    pNode->SetFlag(XFA_NodeFlag_HasRemovedChildren, true);
    m_pDocument->AddPurgeNode(pNode);
    if (IsNeedSavingXMLNode() && pNode->m_pXMLNode) {
      if (pNode->IsAttributeInXML()) {
        ASSERT(pNode->m_pXMLNode == m_pXMLNode &&
               m_pXMLNode->GetType() == FX_XMLNODE_Element);
        if (pNode->m_pXMLNode->GetType() == FX_XMLNODE_Element) {
          CFX_XMLElement* pXMLElement =
              static_cast<CFX_XMLElement*>(pNode->m_pXMLNode);
          WideString wsAttributeName =
              pNode->JSObject()->GetCData(XFA_Attribute::QualifiedName);
          pXMLElement->RemoveAttribute(wsAttributeName.c_str());
        }
  
        WideString wsName = pNode->JSObject()
                                ->TryAttribute(XFA_Attribute::Name, false)
                                .value_or(WideString());
        CFX_XMLElement* pNewXMLElement = new CFX_XMLElement(wsName);
        WideString wsValue = JSObject()->GetCData(XFA_Attribute::Value);
        if (!wsValue.IsEmpty())
          pNewXMLElement->SetTextData(WideString(wsValue));
  
        pNode->m_pXMLNode = pNewXMLElement;
        pNode->JSObject()->SetEnum(XFA_Attribute::Contains,
                                   XFA_AttributeEnum::Unknown, false);
      } else {
        m_pXMLNode->RemoveChildNode(pNode->m_pXMLNode);
      }
      pNode->SetFlag(XFA_NodeFlag_OwnXMLNode, false);
    }
    return true;
  }
  
  CXFA_Node* CXFA_Node::GetFirstChildByName(const WideStringView& wsName) const {
    return GetFirstChildByName(FX_HashCode_GetW(wsName, false));
  }
  
  CXFA_Node* CXFA_Node::GetFirstChildByName(uint32_t dwNameHash) const {
    for (CXFA_Node* pNode = GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetNameHash() == dwNameHash) {
        return pNode;
      }
    }
    return nullptr;
  }
  
  CXFA_Node* CXFA_Node::GetFirstChildByClassInternal(XFA_Element eType) const {
    for (CXFA_Node* pNode = GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() == eType) {
        return pNode;
      }
    }
    return nullptr;
  }
  
  CXFA_Node* CXFA_Node::GetNextSameNameSibling(uint32_t dwNameHash) const {
    for (CXFA_Node* pNode = GetNextSibling(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetNameHash() == dwNameHash) {
        return pNode;
      }
    }
    return nullptr;
  }
  
  CXFA_Node* CXFA_Node::GetNextSameNameSiblingInternal(
      const WideStringView& wsNodeName) const {
    return GetNextSameNameSibling(FX_HashCode_GetW(wsNodeName, false));
  }
  
  CXFA_Node* CXFA_Node::GetNextSameClassSiblingInternal(XFA_Element eType) const {
    for (CXFA_Node* pNode = GetNextSibling(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() == eType) {
        return pNode;
      }
    }
    return nullptr;
  }
  
  int32_t CXFA_Node::GetNodeSameNameIndex() const {
    CFXJSE_Engine* pScriptContext = m_pDocument->GetScriptContext();
    if (!pScriptContext) {
      return -1;
    }
    return pScriptContext->GetIndexByName(const_cast<CXFA_Node*>(this));
  }
  
  int32_t CXFA_Node::GetNodeSameClassIndex() const {
    CFXJSE_Engine* pScriptContext = m_pDocument->GetScriptContext();
    if (!pScriptContext) {
      return -1;
    }
    return pScriptContext->GetIndexByClassName(const_cast<CXFA_Node*>(this));
  }
  
  CXFA_Node* CXFA_Node::GetInstanceMgrOfSubform() {
    CXFA_Node* pInstanceMgr = nullptr;
    if (m_ePacket == XFA_PacketType::Form) {
      CXFA_Node* pParentNode = GetParent();
      if (!pParentNode || pParentNode->GetElementType() == XFA_Element::Area) {
        return pInstanceMgr;
      }
      for (CXFA_Node* pNode = GetPrevSibling(); pNode;
           pNode = pNode->GetPrevSibling()) {
        XFA_Element eType = pNode->GetElementType();
        if ((eType == XFA_Element::Subform || eType == XFA_Element::SubformSet) &&
            pNode->m_dwNameHash != m_dwNameHash) {
          break;
        }
        if (eType == XFA_Element::InstanceManager) {
          WideString wsName = JSObject()->GetCData(XFA_Attribute::Name);
          WideString wsInstName =
              pNode->JSObject()->GetCData(XFA_Attribute::Name);
          if (wsInstName.GetLength() > 0 && wsInstName[0] == '_' &&
              wsInstName.Right(wsInstName.GetLength() - 1) == wsName) {
            pInstanceMgr = pNode;
          }
          break;
        }
      }
    }
    return pInstanceMgr;
  }
  
  CXFA_Occur* CXFA_Node::GetOccurIfExists() {
    return GetFirstChildByClass<CXFA_Occur>(XFA_Element::Occur);
  }
  
  bool CXFA_Node::HasFlag(XFA_NodeFlag dwFlag) const {
    if (m_uNodeFlags & dwFlag)
      return true;
    if (dwFlag == XFA_NodeFlag_HasRemovedChildren)
      return m_pParent && m_pParent->HasFlag(dwFlag);
    return false;
  }
  
  void CXFA_Node::SetFlag(uint32_t dwFlag, bool bNotify) {
    if (dwFlag == XFA_NodeFlag_Initialized && bNotify && !IsInitialized()) {
      CXFA_FFNotify* pNotify = m_pDocument->GetNotify();
      if (pNotify) {
        pNotify->OnNodeReady(this);
      }
    }
    m_uNodeFlags |= dwFlag;
  }
  
  void CXFA_Node::ClearFlag(uint32_t dwFlag) {
    m_uNodeFlags &= ~dwFlag;
  }
  
  void CXFA_Node::ReleaseBindingNodes() {
    // Clear any binding nodes as we don't necessarily destruct in an order that
    // makes sense.
    for (auto& node : binding_nodes_)
      node.Release();
  
    for (CXFA_Node* pNode = m_pChild; pNode; pNode = pNode->m_pNext)
      pNode->ReleaseBindingNodes();
  }
  
  bool CXFA_Node::IsAttributeInXML() {
    return JSObject()->GetEnum(XFA_Attribute::Contains) ==
           XFA_AttributeEnum::MetaData;
  }
  
  void CXFA_Node::OnRemoved(bool bNotify) {
    if (!bNotify)
      return;
  
    CXFA_FFNotify* pNotify = m_pDocument->GetNotify();
    if (pNotify)
      pNotify->OnChildRemoved();
  }
  
  void CXFA_Node::UpdateNameHash() {
    WideString wsName = JSObject()->GetCData(XFA_Attribute::Name);
    m_dwNameHash = FX_HashCode_GetW(wsName.AsStringView(), false);
  }
  
  CFX_XMLNode* CXFA_Node::CreateXMLMappingNode() {
    if (!m_pXMLNode) {
      WideString wsTag(JSObject()->GetCData(XFA_Attribute::Name));
      m_pXMLNode = new CFX_XMLElement(wsTag);
      SetFlag(XFA_NodeFlag_OwnXMLNode, false);
    }
    return m_pXMLNode;
  }
  
  bool CXFA_Node::IsNeedSavingXMLNode() {
    return m_pXMLNode && (GetPacketType() == XFA_PacketType::Datasets ||
                          GetElementType() == XFA_Element::Xfa);
  }
  
  CXFA_Node* CXFA_Node::GetItemIfExists(int32_t iIndex) {
    int32_t iCount = 0;
    uint32_t dwNameHash = 0;
    for (CXFA_Node* pNode = GetNextSibling(); pNode;
         pNode = pNode->GetNextSibling()) {
      XFA_Element eCurType = pNode->GetElementType();
      if (eCurType == XFA_Element::InstanceManager)
        break;
      if ((eCurType != XFA_Element::Subform) &&
          (eCurType != XFA_Element::SubformSet)) {
        continue;
      }
      if (iCount == 0) {
        WideString wsName = pNode->JSObject()->GetCData(XFA_Attribute::Name);
        WideString wsInstName = JSObject()->GetCData(XFA_Attribute::Name);
        if (wsInstName.GetLength() < 1 || wsInstName[0] != '_' ||
            wsInstName.Right(wsInstName.GetLength() - 1) != wsName) {
          return nullptr;
        }
        dwNameHash = pNode->GetNameHash();
      }
      if (dwNameHash != pNode->GetNameHash())
        break;
  
      iCount++;
      if (iCount > iIndex)
        return pNode;
    }
    return nullptr;
  }
  
  int32_t CXFA_Node::GetCount() {
    int32_t iCount = 0;
    uint32_t dwNameHash = 0;
    for (CXFA_Node* pNode = GetNextSibling(); pNode;
         pNode = pNode->GetNextSibling()) {
      XFA_Element eCurType = pNode->GetElementType();
      if (eCurType == XFA_Element::InstanceManager)
        break;
      if ((eCurType != XFA_Element::Subform) &&
          (eCurType != XFA_Element::SubformSet)) {
        continue;
      }
      if (iCount == 0) {
        WideString wsName = pNode->JSObject()->GetCData(XFA_Attribute::Name);
        WideString wsInstName = JSObject()->GetCData(XFA_Attribute::Name);
        if (wsInstName.GetLength() < 1 || wsInstName[0] != '_' ||
            wsInstName.Right(wsInstName.GetLength() - 1) != wsName) {
          return iCount;
        }
        dwNameHash = pNode->GetNameHash();
      }
      if (dwNameHash != pNode->GetNameHash())
        break;
  
      iCount++;
    }
    return iCount;
  }
  
  void CXFA_Node::InsertItem(CXFA_Node* pNewInstance,
                             int32_t iPos,
                             int32_t iCount,
                             bool bMoveDataBindingNodes) {
    if (iCount < 0)
      iCount = GetCount();
    if (iPos < 0)
      iPos = iCount;
    if (iPos == iCount) {
      CXFA_Node* item = GetItemIfExists(iCount - 1);
      if (!item)
        return;
  
      CXFA_Node* pNextSibling =
          iCount > 0 ? item->GetNextSibling() : GetNextSibling();
      GetParent()->InsertChild(pNewInstance, pNextSibling);
      if (bMoveDataBindingNodes) {
        std::set<CXFA_Node*> sNew;
        std::set<CXFA_Node*> sAfter;
        CXFA_NodeIteratorTemplate<CXFA_Node,
                                  CXFA_TraverseStrategy_XFAContainerNode>
            sIteratorNew(pNewInstance);
        for (CXFA_Node* pNode = sIteratorNew.GetCurrent(); pNode;
             pNode = sIteratorNew.MoveToNext()) {
          CXFA_Node* pDataNode = pNode->GetBindData();
          if (!pDataNode)
            continue;
  
          sNew.insert(pDataNode);
        }
        CXFA_NodeIteratorTemplate<CXFA_Node,
                                  CXFA_TraverseStrategy_XFAContainerNode>
            sIteratorAfter(pNextSibling);
        for (CXFA_Node* pNode = sIteratorAfter.GetCurrent(); pNode;
             pNode = sIteratorAfter.MoveToNext()) {
          CXFA_Node* pDataNode = pNode->GetBindData();
          if (!pDataNode)
            continue;
  
          sAfter.insert(pDataNode);
        }
        ReorderDataNodes(sNew, sAfter, false);
      }
    } else {
      CXFA_Node* pBeforeInstance = GetItemIfExists(iPos);
      if (!pBeforeInstance) {
        // TODO(dsinclair): What should happen here?
        return;
      }
  
      GetParent()->InsertChild(pNewInstance, pBeforeInstance);
      if (bMoveDataBindingNodes) {
        std::set<CXFA_Node*> sNew;
        std::set<CXFA_Node*> sBefore;
        CXFA_NodeIteratorTemplate<CXFA_Node,
                                  CXFA_TraverseStrategy_XFAContainerNode>
            sIteratorNew(pNewInstance);
        for (CXFA_Node* pNode = sIteratorNew.GetCurrent(); pNode;
             pNode = sIteratorNew.MoveToNext()) {
          CXFA_Node* pDataNode = pNode->GetBindData();
          if (!pDataNode)
            continue;
  
          sNew.insert(pDataNode);
        }
        CXFA_NodeIteratorTemplate<CXFA_Node,
                                  CXFA_TraverseStrategy_XFAContainerNode>
            sIteratorBefore(pBeforeInstance);
        for (CXFA_Node* pNode = sIteratorBefore.GetCurrent(); pNode;
             pNode = sIteratorBefore.MoveToNext()) {
          CXFA_Node* pDataNode = pNode->GetBindData();
          if (!pDataNode)
            continue;
  
          sBefore.insert(pDataNode);
        }
        ReorderDataNodes(sNew, sBefore, true);
      }
    }
  }
  
  void CXFA_Node::RemoveItem(CXFA_Node* pRemoveInstance,
                             bool bRemoveDataBinding) {
    GetParent()->RemoveChild(pRemoveInstance, true);
    if (!bRemoveDataBinding)
      return;
  
    CXFA_NodeIteratorTemplate<CXFA_Node, CXFA_TraverseStrategy_XFAContainerNode>
        sIterator(pRemoveInstance);
    for (CXFA_Node* pFormNode = sIterator.GetCurrent(); pFormNode;
         pFormNode = sIterator.MoveToNext()) {
      CXFA_Node* pDataNode = pFormNode->GetBindData();
      if (!pDataNode)
        continue;
  
      if (pDataNode->RemoveBindItem(pFormNode) == 0) {
        if (CXFA_Node* pDataParent = pDataNode->GetParent()) {
          pDataParent->RemoveChild(pDataNode, true);
        }
      }
      pFormNode->SetBindingNode(nullptr);
    }
  }
  
  CXFA_Node* CXFA_Node::CreateInstanceIfPossible(bool bDataMerge) {
    CXFA_Document* pDocument = GetDocument();
    CXFA_Node* pTemplateNode = GetTemplateNodeIfExists();
    if (!pTemplateNode)
      return nullptr;
  
    CXFA_Node* pFormParent = GetParent();
    CXFA_Node* pDataScope = nullptr;
    for (CXFA_Node* pRootBoundNode = pFormParent;
         pRootBoundNode && pRootBoundNode->IsContainerNode();
         pRootBoundNode = pRootBoundNode->GetParent()) {
      pDataScope = pRootBoundNode->GetBindData();
      if (pDataScope)
        break;
    }
    if (!pDataScope) {
      pDataScope = ToNode(pDocument->GetXFAObject(XFA_HASHCODE_Record));
      ASSERT(pDataScope);
    }
  
    CXFA_Node* pInstance = pDocument->DataMerge_CopyContainer(
        pTemplateNode, pFormParent, pDataScope, true, bDataMerge, true);
    if (pInstance) {
      pDocument->DataMerge_UpdateBindingRelations(pInstance);
      pFormParent->RemoveChild(pInstance, true);
    }
    return pInstance;
  }
  
  Optional<bool> CXFA_Node::GetDefaultBoolean(XFA_Attribute attr) const {
    Optional<void*> value = GetDefaultValue(attr, XFA_AttributeType::Boolean);
    if (!value)
      return {};
    return {!!*value};
  }
  
  Optional<int32_t> CXFA_Node::GetDefaultInteger(XFA_Attribute attr) const {
    Optional<void*> value = GetDefaultValue(attr, XFA_AttributeType::Integer);
    if (!value)
      return {};
    return {static_cast<int32_t>(reinterpret_cast<uintptr_t>(*value))};
  }
  
  Optional<CXFA_Measurement> CXFA_Node::GetDefaultMeasurement(
      XFA_Attribute attr) const {
    Optional<void*> value = GetDefaultValue(attr, XFA_AttributeType::Measure);
    if (!value)
      return {};
  
    WideString str = WideString(static_cast<const wchar_t*>(*value));
    return {CXFA_Measurement(str.AsStringView())};
  }
  
  Optional<WideString> CXFA_Node::GetDefaultCData(XFA_Attribute attr) const {
    Optional<void*> value = GetDefaultValue(attr, XFA_AttributeType::CData);
    if (!value)
      return {};
  
    return {WideString(static_cast<const wchar_t*>(*value))};
  }
  
  Optional<XFA_AttributeEnum> CXFA_Node::GetDefaultEnum(
      XFA_Attribute attr) const {
    Optional<void*> value = GetDefaultValue(attr, XFA_AttributeType::Enum);
    if (!value)
      return {};
    return {static_cast<XFA_AttributeEnum>(reinterpret_cast<uintptr_t>(*value))};
  }
  
  Optional<void*> CXFA_Node::GetDefaultValue(XFA_Attribute attr,
                                             XFA_AttributeType eType) const {
    const AttributeData* data = GetAttributeData(attr);
    if (!data)
      return {};
    if (data->type == eType)
      return {data->default_value};
    return {};
  }
  
  void CXFA_Node::SendAttributeChangeMessage(XFA_Attribute eAttribute,
                                             bool bScriptModify) {
    CXFA_LayoutProcessor* pLayoutPro = GetDocument()->GetLayoutProcessor();
    if (!pLayoutPro)
      return;
  
    CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
    if (!pNotify)
      return;
  
    if (GetPacketType() != XFA_PacketType::Form) {
      pNotify->OnValueChanged(this, eAttribute, this, this);
      return;
    }
  
    bool bNeedFindContainer = false;
    switch (GetElementType()) {
      case XFA_Element::Caption:
        bNeedFindContainer = true;
        pNotify->OnValueChanged(this, eAttribute, this, GetParent());
        break;
      case XFA_Element::Font:
      case XFA_Element::Para: {
        bNeedFindContainer = true;
        CXFA_Node* pParentNode = GetParent();
        if (pParentNode->GetElementType() == XFA_Element::Caption) {
          pNotify->OnValueChanged(this, eAttribute, pParentNode,
                                  pParentNode->GetParent());
        } else {
          pNotify->OnValueChanged(this, eAttribute, this, pParentNode);
        }
        break;
      }
      case XFA_Element::Margin: {
        bNeedFindContainer = true;
        CXFA_Node* pParentNode = GetParent();
        XFA_Element eParentType = pParentNode->GetElementType();
        if (pParentNode->IsContainerNode()) {
          pNotify->OnValueChanged(this, eAttribute, this, pParentNode);
        } else if (eParentType == XFA_Element::Caption) {
          pNotify->OnValueChanged(this, eAttribute, pParentNode,
                                  pParentNode->GetParent());
        } else {
          CXFA_Node* pNode = pParentNode->GetParent();
          if (pNode && pNode->GetElementType() == XFA_Element::Ui) {
            pNotify->OnValueChanged(this, eAttribute, pNode, pNode->GetParent());
          }
        }
        break;
      }
      case XFA_Element::Comb: {
        CXFA_Node* pEditNode = GetParent();
        XFA_Element eUIType = pEditNode->GetElementType();
        if (pEditNode && (eUIType == XFA_Element::DateTimeEdit ||
                          eUIType == XFA_Element::NumericEdit ||
                          eUIType == XFA_Element::TextEdit)) {
          CXFA_Node* pUINode = pEditNode->GetParent();
          if (pUINode) {
            pNotify->OnValueChanged(this, eAttribute, pUINode,
                                    pUINode->GetParent());
          }
        }
        break;
      }
      case XFA_Element::Button:
      case XFA_Element::Barcode:
      case XFA_Element::ChoiceList:
      case XFA_Element::DateTimeEdit:
      case XFA_Element::NumericEdit:
      case XFA_Element::PasswordEdit:
      case XFA_Element::TextEdit: {
        CXFA_Node* pUINode = GetParent();
        if (pUINode) {
          pNotify->OnValueChanged(this, eAttribute, pUINode,
                                  pUINode->GetParent());
        }
        break;
      }
      case XFA_Element::CheckButton: {
        bNeedFindContainer = true;
        CXFA_Node* pUINode = GetParent();
        if (pUINode) {
          pNotify->OnValueChanged(this, eAttribute, pUINode,
                                  pUINode->GetParent());
        }
        break;
      }
      case XFA_Element::Keep:
      case XFA_Element::Bookend:
      case XFA_Element::Break:
      case XFA_Element::BreakAfter:
      case XFA_Element::BreakBefore:
      case XFA_Element::Overflow:
        bNeedFindContainer = true;
        break;
      case XFA_Element::Area:
      case XFA_Element::Draw:
      case XFA_Element::ExclGroup:
      case XFA_Element::Field:
      case XFA_Element::Subform:
      case XFA_Element::SubformSet:
        pLayoutPro->AddChangedContainer(this);
        pNotify->OnValueChanged(this, eAttribute, this, this);
        break;
      case XFA_Element::Sharptext:
      case XFA_Element::Sharpxml:
      case XFA_Element::SharpxHTML: {
        CXFA_Node* pTextNode = GetParent();
        if (!pTextNode)
          return;
  
        CXFA_Node* pValueNode = pTextNode->GetParent();
        if (!pValueNode)
          return;
  
        XFA_Element eType = pValueNode->GetElementType();
        if (eType == XFA_Element::Value) {
          bNeedFindContainer = true;
          CXFA_Node* pNode = pValueNode->GetParent();
          if (pNode && pNode->IsContainerNode()) {
            if (bScriptModify)
              pValueNode = pNode;
  
            pNotify->OnValueChanged(this, eAttribute, pValueNode, pNode);
          } else {
            pNotify->OnValueChanged(this, eAttribute, pNode, pNode->GetParent());
          }
        } else {
          if (eType == XFA_Element::Items) {
            CXFA_Node* pNode = pValueNode->GetParent();
            if (pNode && pNode->IsContainerNode()) {
              pNotify->OnValueChanged(this, eAttribute, pValueNode, pNode);
            }
          }
        }
        break;
      }
      default:
        break;
    }
  
    if (!bNeedFindContainer)
      return;
  
    CXFA_Node* pParent = this;
    while (pParent && !pParent->IsContainerNode())
      pParent = pParent->GetParent();
  
    if (pParent)
      pLayoutPro->AddChangedContainer(pParent);
  }
  
  void CXFA_Node::SyncValue(const WideString& wsValue, bool bNotify) {
    WideString wsFormatValue = wsValue;
    CXFA_Node* pContainerNode = GetContainerNode();
    if (pContainerNode)
      wsFormatValue = pContainerNode->GetFormatDataValue(wsValue);
  
    JSObject()->SetContent(wsValue, wsFormatValue, bNotify, false, true);
  }
  
  WideString CXFA_Node::GetRawValue() {
    return JSObject()->GetContent(false);
  }
  
  int32_t CXFA_Node::GetRotate() {
    Optional<int32_t> degrees =
        JSObject()->TryInteger(XFA_Attribute::Rotate, false);
    return degrees ? XFA_MapRotation(*degrees) / 90 * 90 : 0;
  }
  
  CXFA_Border* CXFA_Node::GetBorderIfExists() const {
    return JSObject()->GetProperty<CXFA_Border>(0, XFA_Element::Border);
  }
  
  CXFA_Border* CXFA_Node::GetOrCreateBorderIfPossible() {
    return JSObject()->GetOrCreateProperty<CXFA_Border>(0, XFA_Element::Border);
  }
  
  CXFA_Caption* CXFA_Node::GetCaptionIfExists() const {
    return JSObject()->GetProperty<CXFA_Caption>(0, XFA_Element::Caption);
  }
  
  CXFA_Font* CXFA_Node::GetOrCreateFontIfPossible() {
    return JSObject()->GetOrCreateProperty<CXFA_Font>(0, XFA_Element::Font);
  }
  
  CXFA_Font* CXFA_Node::GetFontIfExists() const {
    return JSObject()->GetProperty<CXFA_Font>(0, XFA_Element::Font);
  }
  
  float CXFA_Node::GetFontSize() const {
    CXFA_Font* font = GetFontIfExists();
    float fFontSize = font ? font->GetFontSize() : 10.0f;
    return fFontSize < 0.1f ? 10.0f : fFontSize;
  }
  
  float CXFA_Node::GetLineHeight() const {
    float fLineHeight = 0;
    CXFA_Para* para = GetParaIfExists();
    if (para)
      fLineHeight = para->GetLineHeight();
  
    if (fLineHeight < 1)
      fLineHeight = GetFontSize() * 1.2f;
    return fLineHeight;
  }
  
  FX_ARGB CXFA_Node::GetTextColor() const {
    CXFA_Font* font = GetFontIfExists();
    return font ? font->GetColor() : 0xFF000000;
  }
  
  CXFA_Margin* CXFA_Node::GetMarginIfExists() const {
    return JSObject()->GetProperty<CXFA_Margin>(0, XFA_Element::Margin);
  }
  
  CXFA_Para* CXFA_Node::GetParaIfExists() const {
    return JSObject()->GetProperty<CXFA_Para>(0, XFA_Element::Para);
  }
  
  bool CXFA_Node::IsOpenAccess() {
    for (auto* pNode = this; pNode; pNode = pNode->GetContainerParent()) {
      XFA_AttributeEnum iAcc = pNode->JSObject()->GetEnum(XFA_Attribute::Access);
      if (iAcc != XFA_AttributeEnum::Open)
        return false;
    }
    return true;
  }
  
  CXFA_Value* CXFA_Node::GetDefaultValueIfExists() {
    CXFA_Node* pTemNode = GetTemplateNodeIfExists();
    return pTemNode ? pTemNode->JSObject()->GetProperty<CXFA_Value>(
                          0, XFA_Element::Value)
                    : nullptr;
  }
  
  CXFA_Value* CXFA_Node::GetFormValueIfExists() const {
    return JSObject()->GetProperty<CXFA_Value>(0, XFA_Element::Value);
  }
  
  CXFA_Calculate* CXFA_Node::GetCalculateIfExists() const {
    return JSObject()->GetProperty<CXFA_Calculate>(0, XFA_Element::Calculate);
  }
  
  CXFA_Validate* CXFA_Node::GetValidateIfExists() const {
    return JSObject()->GetProperty<CXFA_Validate>(0, XFA_Element::Validate);
  }
  
  CXFA_Validate* CXFA_Node::GetOrCreateValidateIfPossible() {
    return JSObject()->GetOrCreateProperty<CXFA_Validate>(0,
                                                          XFA_Element::Validate);
  }
  
  CXFA_Bind* CXFA_Node::GetBindIfExists() const {
    return JSObject()->GetProperty<CXFA_Bind>(0, XFA_Element::Bind);
  }
  
  Optional<float> CXFA_Node::TryWidth() {
    return JSObject()->TryMeasureAsFloat(XFA_Attribute::W);
  }
  
  Optional<float> CXFA_Node::TryHeight() {
    return JSObject()->TryMeasureAsFloat(XFA_Attribute::H);
  }
  
  Optional<float> CXFA_Node::TryMinWidth() {
    return JSObject()->TryMeasureAsFloat(XFA_Attribute::MinW);
  }
  
  Optional<float> CXFA_Node::TryMinHeight() {
    return JSObject()->TryMeasureAsFloat(XFA_Attribute::MinH);
  }
  
  Optional<float> CXFA_Node::TryMaxWidth() {
    return JSObject()->TryMeasureAsFloat(XFA_Attribute::MaxW);
  }
  
  Optional<float> CXFA_Node::TryMaxHeight() {
    return JSObject()->TryMeasureAsFloat(XFA_Attribute::MaxH);
  }
  
  CXFA_Node* CXFA_Node::GetExclGroupIfExists() {
    CXFA_Node* pExcl = GetParent();
    if (!pExcl || pExcl->GetElementType() != XFA_Element::ExclGroup)
      return nullptr;
    return pExcl;
  }
  
  int32_t CXFA_Node::ProcessEvent(CXFA_FFDocView* docView,
                                  XFA_AttributeEnum iActivity,
                                  CXFA_EventParam* pEventParam) {
    if (GetElementType() == XFA_Element::Draw)
      return XFA_EVENTERROR_NotExist;
  
    std::vector<CXFA_Event*> eventArray =
        GetEventByActivity(iActivity, pEventParam->m_bIsFormReady);
    bool first = true;
    int32_t iRet = XFA_EVENTERROR_NotExist;
    for (CXFA_Event* event : eventArray) {
      int32_t result = ProcessEvent(docView, event, pEventParam);
      if (first || result == XFA_EVENTERROR_Success)
        iRet = result;
      first = false;
    }
    return iRet;
  }
  
  int32_t CXFA_Node::ProcessEvent(CXFA_FFDocView* docView,
                                  CXFA_Event* event,
                                  CXFA_EventParam* pEventParam) {
    if (!event)
      return XFA_EVENTERROR_NotExist;
  
    switch (event->GetEventType()) {
      case XFA_Element::Execute:
        break;
      case XFA_Element::Script:
        return ExecuteScript(docView, event->GetScriptIfExists(), pEventParam);
      case XFA_Element::SignData:
        break;
      case XFA_Element::Submit: {
        CXFA_Submit* submit = event->GetSubmitIfExists();
        if (!submit)
          return XFA_EVENTERROR_NotExist;
        return docView->GetDoc()->GetDocEnvironment()->Submit(docView->GetDoc(),
                                                              submit);
      }
      default:
        break;
    }
    return XFA_EVENTERROR_NotExist;
  }
  
  int32_t CXFA_Node::ProcessCalculate(CXFA_FFDocView* docView) {
    if (GetElementType() == XFA_Element::Draw)
      return XFA_EVENTERROR_NotExist;
  
    CXFA_Calculate* calc = GetCalculateIfExists();
    if (!calc)
      return XFA_EVENTERROR_NotExist;
    if (IsUserInteractive())
      return XFA_EVENTERROR_Disabled;
  
    CXFA_EventParam EventParam;
    EventParam.m_eType = XFA_EVENT_Calculate;
    int32_t iRet = ExecuteScript(docView, calc->GetScriptIfExists(), &EventParam);
    if (iRet != XFA_EVENTERROR_Success)
      return iRet;
  
    if (GetRawValue() != EventParam.m_wsResult) {
      SetValue(XFA_VALUEPICTURE_Raw, EventParam.m_wsResult);
      UpdateUIDisplay(docView, nullptr);
    }
    return XFA_EVENTERROR_Success;
  }
  
  void CXFA_Node::ProcessScriptTestValidate(CXFA_FFDocView* docView,
                                            CXFA_Validate* validate,
                                            int32_t iRet,
                                            bool bRetValue,
                                            bool bVersionFlag) {
    if (iRet != XFA_EVENTERROR_Success)
      return;
    if (bRetValue)
      return;
  
    IXFA_AppProvider* pAppProvider =
        docView->GetDoc()->GetApp()->GetAppProvider();
    if (!pAppProvider)
      return;
  
    WideString wsTitle = pAppProvider->GetAppTitle();
    WideString wsScriptMsg = validate->GetScriptMessageText();
    if (validate->GetScriptTest() == XFA_AttributeEnum::Warning) {
      if (IsUserInteractive())
        return;
      if (wsScriptMsg.IsEmpty())
        wsScriptMsg = GetValidateMessage(false, bVersionFlag);
  
      if (bVersionFlag) {
        pAppProvider->MsgBox(wsScriptMsg, wsTitle, XFA_MBICON_Warning, XFA_MB_OK);
        return;
      }
      if (pAppProvider->MsgBox(wsScriptMsg, wsTitle, XFA_MBICON_Warning,
                               XFA_MB_YesNo) == XFA_IDYes) {
        SetFlag(XFA_NodeFlag_UserInteractive, false);
      }
      return;
    }
  
    if (wsScriptMsg.IsEmpty())
      wsScriptMsg = GetValidateMessage(true, bVersionFlag);
    pAppProvider->MsgBox(wsScriptMsg, wsTitle, XFA_MBICON_Error, XFA_MB_OK);
  }
  
  int32_t CXFA_Node::ProcessFormatTestValidate(CXFA_FFDocView* docView,
                                               CXFA_Validate* validate,
                                               bool bVersionFlag) {
    WideString wsRawValue = GetRawValue();
    if (!wsRawValue.IsEmpty()) {
      WideString wsPicture = validate->GetPicture();
      if (wsPicture.IsEmpty())
        return XFA_EVENTERROR_NotExist;
  
      IFX_Locale* pLocale = GetLocale();
      if (!pLocale)
        return XFA_EVENTERROR_NotExist;
  
      CXFA_LocaleValue lcValue = XFA_GetLocaleValue(this);
      if (!lcValue.ValidateValue(lcValue.GetValue(), wsPicture, pLocale,
                                 nullptr)) {
        IXFA_AppProvider* pAppProvider =
            docView->GetDoc()->GetApp()->GetAppProvider();
        if (!pAppProvider)
          return XFA_EVENTERROR_NotExist;
  
        WideString wsFormatMsg = validate->GetFormatMessageText();
        WideString wsTitle = pAppProvider->GetAppTitle();
        if (validate->GetFormatTest() == XFA_AttributeEnum::Error) {
          if (wsFormatMsg.IsEmpty())
            wsFormatMsg = GetValidateMessage(true, bVersionFlag);
          pAppProvider->MsgBox(wsFormatMsg, wsTitle, XFA_MBICON_Error, XFA_MB_OK);
          return XFA_EVENTERROR_Success;
        }
        if (IsUserInteractive())
          return XFA_EVENTERROR_NotExist;
        if (wsFormatMsg.IsEmpty())
          wsFormatMsg = GetValidateMessage(false, bVersionFlag);
  
        if (bVersionFlag) {
          pAppProvider->MsgBox(wsFormatMsg, wsTitle, XFA_MBICON_Warning,
                               XFA_MB_OK);
          return XFA_EVENTERROR_Success;
        }
        if (pAppProvider->MsgBox(wsFormatMsg, wsTitle, XFA_MBICON_Warning,
                                 XFA_MB_YesNo) == XFA_IDYes) {
          SetFlag(XFA_NodeFlag_UserInteractive, false);
        }
        return XFA_EVENTERROR_Success;
      }
    }
    return XFA_EVENTERROR_NotExist;
  }
  
  int32_t CXFA_Node::ProcessNullTestValidate(CXFA_FFDocView* docView,
                                             CXFA_Validate* validate,
                                             int32_t iFlags,
                                             bool bVersionFlag) {
    if (!GetValue(XFA_VALUEPICTURE_Raw).IsEmpty())
      return XFA_EVENTERROR_Success;
    if (m_bIsNull && m_bPreNull)
      return XFA_EVENTERROR_Success;
  
    XFA_AttributeEnum eNullTest = validate->GetNullTest();
    WideString wsNullMsg = validate->GetNullMessageText();
    if (iFlags & 0x01) {
      int32_t iRet = XFA_EVENTERROR_Success;
      if (eNullTest != XFA_AttributeEnum::Disabled)
        iRet = XFA_EVENTERROR_Error;
  
      if (!wsNullMsg.IsEmpty()) {
        if (eNullTest != XFA_AttributeEnum::Disabled) {
          docView->m_arrNullTestMsg.push_back(wsNullMsg);
          return XFA_EVENTERROR_Error;
        }
        return XFA_EVENTERROR_Success;
      }
      return iRet;
    }
    if (wsNullMsg.IsEmpty() && bVersionFlag &&
        eNullTest != XFA_AttributeEnum::Disabled) {
      return XFA_EVENTERROR_Error;
    }
    IXFA_AppProvider* pAppProvider =
        docView->GetDoc()->GetApp()->GetAppProvider();
    if (!pAppProvider)
      return XFA_EVENTERROR_NotExist;
  
    WideString wsCaptionName;
    WideString wsTitle = pAppProvider->GetAppTitle();
    switch (eNullTest) {
      case XFA_AttributeEnum::Error: {
        if (wsNullMsg.IsEmpty()) {
          wsCaptionName = GetValidateCaptionName(bVersionFlag);
          wsNullMsg =
              WideString::Format(L"%ls cannot be blank.", wsCaptionName.c_str());
        }
        pAppProvider->MsgBox(wsNullMsg, wsTitle, XFA_MBICON_Status, XFA_MB_OK);
        return XFA_EVENTERROR_Error;
      }
      case XFA_AttributeEnum::Warning: {
        if (IsUserInteractive())
          return true;
  
        if (wsNullMsg.IsEmpty()) {
          wsCaptionName = GetValidateCaptionName(bVersionFlag);
          wsNullMsg = WideString::Format(
              L"%ls cannot be blank. To ignore validations for %ls, click "
              L"Ignore.",
              wsCaptionName.c_str(), wsCaptionName.c_str());
        }
        if (pAppProvider->MsgBox(wsNullMsg, wsTitle, XFA_MBICON_Warning,
                                 XFA_MB_YesNo) == XFA_IDYes) {
          SetFlag(XFA_NodeFlag_UserInteractive, false);
        }
        return XFA_EVENTERROR_Error;
      }
      case XFA_AttributeEnum::Disabled:
      default:
        break;
    }
    return XFA_EVENTERROR_Success;
  }
  
  int32_t CXFA_Node::ProcessValidate(CXFA_FFDocView* docView, int32_t iFlags) {
    if (GetElementType() == XFA_Element::Draw)
      return XFA_EVENTERROR_NotExist;
  
    CXFA_Validate* validate = GetValidateIfExists();
    if (!validate)
      return XFA_EVENTERROR_NotExist;
  
    bool bInitDoc = validate->NeedsInitApp();
    bool bStatus = docView->GetLayoutStatus() < XFA_DOCVIEW_LAYOUTSTATUS_End;
    int32_t iFormat = 0;
    int32_t iRet = XFA_EVENTERROR_NotExist;
    CXFA_Script* script = validate->GetScriptIfExists();
    bool bRet = false;
    bool hasBoolResult = (bInitDoc || bStatus) && GetRawValue().IsEmpty();
    if (script) {
      CXFA_EventParam eParam;
      eParam.m_eType = XFA_EVENT_Validate;
      eParam.m_pTarget = this;
      std::tie(iRet, bRet) = ExecuteBoolScript(docView, script, &eParam);
    }
  
    XFA_VERSION version = docView->GetDoc()->GetXFADoc()->GetCurVersionMode();
    bool bVersionFlag = false;
    if (version < XFA_VERSION_208)
      bVersionFlag = true;
  
    if (bInitDoc) {
      validate->ClearFlag(XFA_NodeFlag_NeedsInitApp);
    } else {
      iFormat = ProcessFormatTestValidate(docView, validate, bVersionFlag);
      if (!bVersionFlag) {
        bVersionFlag =
            docView->GetDoc()->GetXFADoc()->HasFlag(XFA_DOCFLAG_Scripting);
      }
  
      iRet |= ProcessNullTestValidate(docView, validate, iFlags, bVersionFlag);
    }
  
    if (iFormat != XFA_EVENTERROR_Success && hasBoolResult)
      ProcessScriptTestValidate(docView, validate, iRet, bRet, bVersionFlag);
  
    return iRet | iFormat;
  }
  
  WideString CXFA_Node::GetValidateCaptionName(bool bVersionFlag) {
    WideString wsCaptionName;
  
    if (!bVersionFlag) {
      CXFA_Caption* caption = GetCaptionIfExists();
      if (caption) {
        CXFA_Value* capValue = caption->GetValueIfExists();
        if (capValue) {
          CXFA_Text* captionText = capValue->GetTextIfExists();
          if (captionText)
            wsCaptionName = captionText->GetContent();
        }
      }
    }
    if (!wsCaptionName.IsEmpty())
      return wsCaptionName;
    return JSObject()->GetCData(XFA_Attribute::Name);
  }
  
  WideString CXFA_Node::GetValidateMessage(bool bError, bool bVersionFlag) {
    WideString wsCaptionName = GetValidateCaptionName(bVersionFlag);
    if (bVersionFlag)
      return WideString::Format(L"%ls validation failed", wsCaptionName.c_str());
    if (bError) {
      return WideString::Format(L"The value you entered for %ls is invalid.",
                                wsCaptionName.c_str());
    }
    return WideString::Format(
        L"The value you entered for %ls is invalid. To ignore "
        L"validations for %ls, click Ignore.",
        wsCaptionName.c_str(), wsCaptionName.c_str());
  }
  
  int32_t CXFA_Node::ExecuteScript(CXFA_FFDocView* docView,
                                   CXFA_Script* script,
                                   CXFA_EventParam* pEventParam) {
    bool bRet;
    int32_t iRet;
    std::tie(iRet, bRet) = ExecuteBoolScript(docView, script, pEventParam);
    return iRet;
  }
  
  std::pair<int32_t, bool> CXFA_Node::ExecuteBoolScript(
      CXFA_FFDocView* docView,
      CXFA_Script* script,
      CXFA_EventParam* pEventParam) {
    if (m_ExecuteRecursionDepth > kMaxExecuteRecursion)
      return {XFA_EVENTERROR_Success, false};
  
    ASSERT(pEventParam);
    if (!script)
      return {XFA_EVENTERROR_NotExist, false};
    if (script->GetRunAt() == XFA_AttributeEnum::Server)
      return {XFA_EVENTERROR_Disabled, false};
  
    WideString wsExpression = script->GetExpression();
    if (wsExpression.IsEmpty())
      return {XFA_EVENTERROR_NotExist, false};
  
    CXFA_Script::Type eScriptType = script->GetContentType();
    if (eScriptType == CXFA_Script::Type::Unknown)
      return {XFA_EVENTERROR_Success, false};
  
    CXFA_FFDoc* pDoc = docView->GetDoc();
    CFXJSE_Engine* pContext = pDoc->GetXFADoc()->GetScriptContext();
    pContext->SetEventParam(*pEventParam);
    pContext->SetRunAtType(script->GetRunAt());
  
    std::vector<CXFA_Node*> refNodes;
    if (pEventParam->m_eType == XFA_EVENT_InitCalculate ||
        pEventParam->m_eType == XFA_EVENT_Calculate) {
      pContext->SetNodesOfRunScript(&refNodes);
    }
  
    auto pTmpRetValue = pdfium::MakeUnique<CFXJSE_Value>(pContext->GetIsolate());
    bool bRet = false;
    {
      AutoRestorer<uint8_t> restorer(&m_ExecuteRecursionDepth);
      ++m_ExecuteRecursionDepth;
      bRet = pContext->RunScript(eScriptType, wsExpression.AsStringView(),
                                 pTmpRetValue.get(), this);
    }
  
    int32_t iRet = XFA_EVENTERROR_Error;
    if (bRet) {
      iRet = XFA_EVENTERROR_Success;
      if (pEventParam->m_eType == XFA_EVENT_Calculate ||
          pEventParam->m_eType == XFA_EVENT_InitCalculate) {
        if (!pTmpRetValue->IsUndefined()) {
          if (!pTmpRetValue->IsNull())
            pEventParam->m_wsResult = pTmpRetValue->ToWideString();
  
          iRet = XFA_EVENTERROR_Success;
        } else {
          iRet = XFA_EVENTERROR_Error;
        }
        if (pEventParam->m_eType == XFA_EVENT_InitCalculate) {
          if ((iRet == XFA_EVENTERROR_Success) &&
              (GetRawValue() != pEventParam->m_wsResult)) {
            SetValue(XFA_VALUEPICTURE_Raw, pEventParam->m_wsResult);
            docView->AddValidateNode(this);
          }
        }
        for (CXFA_Node* pRefNode : refNodes) {
          if (pRefNode == this)
            continue;
  
          CXFA_CalcData* pGlobalData = pRefNode->JSObject()->GetCalcData();
          if (!pGlobalData) {
            pRefNode->JSObject()->SetCalcData(
                pdfium::MakeUnique<CXFA_CalcData>());
            pGlobalData = pRefNode->JSObject()->GetCalcData();
          }
          if (!pdfium::ContainsValue(pGlobalData->m_Globals, this))
            pGlobalData->m_Globals.push_back(this);
        }
      }
    }
    pContext->SetNodesOfRunScript(nullptr);
  
    return {iRet, pTmpRetValue->IsBoolean() ? pTmpRetValue->ToBoolean() : false};
  }
  
  WideString CXFA_Node::GetBarcodeType() {
    CXFA_Node* pUIChild = GetUIChild();
    return pUIChild
               ? WideString(pUIChild->JSObject()->GetCData(XFA_Attribute::Type))
               : WideString();
  }
  
  Optional<BC_CHAR_ENCODING> CXFA_Node::GetBarcodeAttribute_CharEncoding() {
    Optional<WideString> wsCharEncoding =
        GetUIChild()->JSObject()->TryCData(XFA_Attribute::CharEncoding, true);
    if (!wsCharEncoding)
      return {};
    if (wsCharEncoding->CompareNoCase(L"UTF-16"))
      return {CHAR_ENCODING_UNICODE};
    if (wsCharEncoding->CompareNoCase(L"UTF-8"))
      return {CHAR_ENCODING_UTF8};
    return {};
  }
  
  Optional<bool> CXFA_Node::GetBarcodeAttribute_Checksum() {
    Optional<XFA_AttributeEnum> checksum =
        GetUIChild()->JSObject()->TryEnum(XFA_Attribute::Checksum, true);
    if (!checksum)
      return {};
  
    switch (*checksum) {
      case XFA_AttributeEnum::None:
        return {false};
      case XFA_AttributeEnum::Auto:
        return {true};
      case XFA_AttributeEnum::Checksum_1mod10:
      case XFA_AttributeEnum::Checksum_1mod10_1mod11:
      case XFA_AttributeEnum::Checksum_2mod10:
      default:
        break;
    }
    return {};
  }
  
  Optional<int32_t> CXFA_Node::GetBarcodeAttribute_DataLength() {
    Optional<WideString> wsDataLength =
        GetUIChild()->JSObject()->TryCData(XFA_Attribute::DataLength, true);
    if (!wsDataLength)
      return {};
  
    return {FXSYS_wtoi(wsDataLength->c_str())};
  }
  
  Optional<char> CXFA_Node::GetBarcodeAttribute_StartChar() {
    Optional<WideString> wsStartEndChar =
        GetUIChild()->JSObject()->TryCData(XFA_Attribute::StartChar, true);
    if (!wsStartEndChar || wsStartEndChar->IsEmpty())
      return {};
  
    return {static_cast<char>((*wsStartEndChar)[0])};
  }
  
  Optional<char> CXFA_Node::GetBarcodeAttribute_EndChar() {
    Optional<WideString> wsStartEndChar =
        GetUIChild()->JSObject()->TryCData(XFA_Attribute::EndChar, true);
    if (!wsStartEndChar || wsStartEndChar->IsEmpty())
      return {};
  
    return {static_cast<char>((*wsStartEndChar)[0])};
  }
  
  Optional<int32_t> CXFA_Node::GetBarcodeAttribute_ECLevel() {
    Optional<WideString> wsECLevel = GetUIChild()->JSObject()->TryCData(
        XFA_Attribute::ErrorCorrectionLevel, true);
    if (!wsECLevel)
      return {};
    return {FXSYS_wtoi(wsECLevel->c_str())};
  }
  
  Optional<int32_t> CXFA_Node::GetBarcodeAttribute_ModuleWidth() {
    Optional<CXFA_Measurement> moduleWidthHeight =
        GetUIChild()->JSObject()->TryMeasure(XFA_Attribute::ModuleWidth, true);
    if (!moduleWidthHeight)
      return {};
  
    return {static_cast<int32_t>(moduleWidthHeight->ToUnit(XFA_Unit::Pt))};
  }
  
  Optional<int32_t> CXFA_Node::GetBarcodeAttribute_ModuleHeight() {
    Optional<CXFA_Measurement> moduleWidthHeight =
        GetUIChild()->JSObject()->TryMeasure(XFA_Attribute::ModuleHeight, true);
    if (!moduleWidthHeight)
      return {};
  
    return {static_cast<int32_t>(moduleWidthHeight->ToUnit(XFA_Unit::Pt))};
  }
  
  Optional<bool> CXFA_Node::GetBarcodeAttribute_PrintChecksum() {
    return GetUIChild()->JSObject()->TryBoolean(XFA_Attribute::PrintCheckDigit,
                                                true);
  }
  
  Optional<BC_TEXT_LOC> CXFA_Node::GetBarcodeAttribute_TextLocation() {
    Optional<XFA_AttributeEnum> textLocation =
        GetUIChild()->JSObject()->TryEnum(XFA_Attribute::TextLocation, true);
    if (!textLocation)
      return {};
  
    switch (*textLocation) {
      case XFA_AttributeEnum::None:
        return {BC_TEXT_LOC_NONE};
      case XFA_AttributeEnum::Above:
        return {BC_TEXT_LOC_ABOVE};
      case XFA_AttributeEnum::Below:
        return {BC_TEXT_LOC_BELOW};
      case XFA_AttributeEnum::AboveEmbedded:
        return {BC_TEXT_LOC_ABOVEEMBED};
      case XFA_AttributeEnum::BelowEmbedded:
        return {BC_TEXT_LOC_BELOWEMBED};
      default:
        break;
    }
    return {};
  }
  
  Optional<bool> CXFA_Node::GetBarcodeAttribute_Truncate() {
    return GetUIChild()->JSObject()->TryBoolean(XFA_Attribute::Truncate, true);
  }
  
  Optional<int8_t> CXFA_Node::GetBarcodeAttribute_WideNarrowRatio() {
    Optional<WideString> wsWideNarrowRatio =
        GetUIChild()->JSObject()->TryCData(XFA_Attribute::WideNarrowRatio, true);
    if (!wsWideNarrowRatio)
      return {};
  
    Optional<size_t> ptPos = wsWideNarrowRatio->Find(':');
    if (!ptPos)
      return {static_cast<int8_t>(FXSYS_wtoi(wsWideNarrowRatio->c_str()))};
  
    int32_t fB = FXSYS_wtoi(
        wsWideNarrowRatio->Right(wsWideNarrowRatio->GetLength() - (*ptPos + 1))
            .c_str());
    if (!fB)
      return {0};
  
    int32_t fA = FXSYS_wtoi(wsWideNarrowRatio->Left(*ptPos).c_str());
    float result = static_cast<float>(fA) / static_cast<float>(fB);
    return {static_cast<int8_t>(result)};
  }
  
  std::pair<XFA_Element, CXFA_Node*> CXFA_Node::CreateUIChild() {
    XFA_Element eType = GetElementType();
    ASSERT(eType == XFA_Element::Field || eType == XFA_Element::Draw);
  
    // Both Field and Draw have a UI property. We should always be able to
    // retrieve or create the UI element. If we can't something is wrong.
    CXFA_Ui* pUI = JSObject()->GetOrCreateProperty<CXFA_Ui>(0, XFA_Element::Ui);
    ASSERT(pUI);
  
    CXFA_Node* pUIChild = nullptr;
    // Search through the children of the UI node to see if we have any of our
    // One-Of entries. If so, that is the node associated with our UI.
    for (CXFA_Node* pChild = pUI->GetFirstChild(); pChild;
         pChild = pChild->GetNextSibling()) {
      if (pUI->IsAOneOfChild(pChild)) {
        pUIChild = pChild;
        break;
      }
    }
  
    XFA_Element eWidgetType = XFA_Element::Unknown;
    XFA_Element valueNodeType = XFA_Element::Unknown;
  
    // Both Field and Draw nodes have a Value child. So, we should either always
    // have it, or always create it. If we don't get the Value child for some
    // reason something has gone really wrong.
    CXFA_Value* value =
        JSObject()->GetOrCreateProperty<CXFA_Value>(0, XFA_Element::Value);
    ASSERT(value);
  
    // The Value nodes only have One-Of children. So, if we have a first child
    // that child must be the type we want to use.
    CXFA_Node* child = value->GetFirstChild();
    if (child) {
      switch (child->GetElementType()) {
        case XFA_Element::Boolean:
          valueNodeType = XFA_Element::CheckButton;
          break;
        case XFA_Element::Integer:
        case XFA_Element::Decimal:
        case XFA_Element::Float:
          valueNodeType = XFA_Element::NumericEdit;
          break;
        case XFA_Element::ExData:
        case XFA_Element::Text:
          valueNodeType = XFA_Element::TextEdit;
          eWidgetType = XFA_Element::Text;
          break;
        case XFA_Element::Date:
        case XFA_Element::Time:
        case XFA_Element::DateTime:
          valueNodeType = XFA_Element::DateTimeEdit;
          break;
        case XFA_Element::Image:
          valueNodeType = XFA_Element::ImageEdit;
          eWidgetType = XFA_Element::Image;
          break;
        case XFA_Element::Arc:
        case XFA_Element::Line:
        case XFA_Element::Rectangle:
          valueNodeType = XFA_Element::DefaultUi;
          eWidgetType = child->GetElementType();
          break;
        default:
          NOTREACHED();
          break;
      }
    }
  
    if (eType == XFA_Element::Draw) {
      if (pUIChild && pUIChild->GetElementType() == XFA_Element::TextEdit) {
        eWidgetType = XFA_Element::Text;
      } else if (pUIChild &&
                 pUIChild->GetElementType() == XFA_Element::ImageEdit) {
        eWidgetType = XFA_Element::Image;
      } else if (eWidgetType == XFA_Element::Unknown) {
        eWidgetType = XFA_Element::Text;
      }
    } else if (eType == XFA_Element::Field) {
      if (pUIChild && pUIChild->GetElementType() == XFA_Element::DefaultUi) {
        eWidgetType = XFA_Element::TextEdit;
      } else if (pUIChild) {
        eWidgetType = pUIChild->GetElementType();
      } else if (valueNodeType == XFA_Element::Unknown) {
        eWidgetType = XFA_Element::TextEdit;
      } else {
        eWidgetType = valueNodeType;
      }
    } else {
      NOTREACHED();
    }
  
    if (!pUIChild) {
      if (valueNodeType == XFA_Element::Unknown)
        valueNodeType = XFA_Element::TextEdit;
      pUIChild =
          pUI->JSObject()->GetOrCreateProperty<CXFA_Node>(0, valueNodeType);
    }
  
    CreateValueNodeIfNeeded(value, pUIChild);
    return {eWidgetType, pUIChild};
  }
  
  void CXFA_Node::CreateValueNodeIfNeeded(CXFA_Value* value,
                                          CXFA_Node* pUIChild) {
    // Value nodes only have one child. If we have one already we're done.
    if (value->GetFirstChild() != nullptr)
      return;
  
    // Create the Value node for our UI if needed.
    XFA_Element valueType = pUIChild->GetValueNodeType();
    if (pUIChild->GetElementType() == XFA_Element::CheckButton) {
      CXFA_Items* pItems = GetChild<CXFA_Items>(0, XFA_Element::Items, false);
      if (pItems) {
        CXFA_Node* pItem =
            pItems->GetChild<CXFA_Node>(0, XFA_Element::Unknown, false);
        if (pItem)
          valueType = pItem->GetElementType();
      }
    }
    value->JSObject()->GetOrCreateProperty<CXFA_Node>(0, valueType);
  }
  
  XFA_Element CXFA_Node::GetValueNodeType() const {
    return XFA_Element::Text;
  }
  
  CXFA_Node* CXFA_Node::GetUIChild() {
    if (m_eUIType != XFA_Element::Unknown)
      return m_pUiChildNode;
  
    XFA_Element type = GetElementType();
    if (type == XFA_Element::Field || type == XFA_Element::Draw) {
      std::tie(m_eUIType, m_pUiChildNode) = CreateUIChild();
    } else {
      m_eUIType = GetElementType();
      m_pUiChildNode = nullptr;
    }
    return m_pUiChildNode;
  }
  
  XFA_Element CXFA_Node::GetUIType() {
    GetUIChild();
    return m_eUIType;
  }
  
  CXFA_Border* CXFA_Node::GetUIBorder() {
    CXFA_Node* pUIChild = GetUIChild();
    return pUIChild ? pUIChild->JSObject()->GetProperty<CXFA_Border>(
                          0, XFA_Element::Border)
                    : nullptr;
  }
  
  CFX_RectF CXFA_Node::GetUIMargin() {
    CXFA_Node* pUIChild = GetUIChild();
    if (!pUIChild)
      return CFX_RectF();
  
    CXFA_Margin* mgUI =
        pUIChild->JSObject()->GetProperty<CXFA_Margin>(0, XFA_Element::Margin);
    if (!mgUI)
      return CFX_RectF();
  
    CXFA_Border* border = GetUIBorder();
    if (border && border->GetPresence() != XFA_AttributeEnum::Visible)
      return CFX_RectF();
  
    Optional<float> left = mgUI->TryLeftInset();
    Optional<float> top = mgUI->TryTopInset();
    Optional<float> right = mgUI->TryRightInset();
    Optional<float> bottom = mgUI->TryBottomInset();
    if (border) {
      bool bVisible = false;
      float fThickness = 0;
      XFA_AttributeEnum iType = XFA_AttributeEnum::Unknown;
      std::tie(iType, bVisible, fThickness) = border->Get3DStyle();
      if (!left || !top || !right || !bottom) {
        std::vector<CXFA_Stroke*> strokes = border->GetStrokes();
        if (!top)
          top = GetEdgeThickness(strokes, bVisible, 0);
        if (!right)
          right = GetEdgeThickness(strokes, bVisible, 1);
        if (!bottom)
          bottom = GetEdgeThickness(strokes, bVisible, 2);
        if (!left)
          left = GetEdgeThickness(strokes, bVisible, 3);
      }
    }
    return CFX_RectF(left.value_or(0.0), top.value_or(0.0), right.value_or(0.0),
                     bottom.value_or(0.0));
  }
  
  std::vector<CXFA_Event*> CXFA_Node::GetEventByActivity(
      XFA_AttributeEnum iActivity,
      bool bIsFormReady) {
    std::vector<CXFA_Event*> events;
    for (CXFA_Node* node : GetNodeList(0, XFA_Element::Event)) {
      auto* event = static_cast<CXFA_Event*>(node);
      if (event->GetActivity() != iActivity)
        continue;
  
      if (iActivity != XFA_AttributeEnum::Ready) {
        events.push_back(event);
        continue;
      }
  
      WideString wsRef = event->GetRef();
      if (bIsFormReady) {
        if (wsRef == WideStringView(L"$form"))
          events.push_back(event);
        continue;
      }
  
      if (wsRef == WideStringView(L"$layout"))
        events.push_back(event);
    }
    return events;
  }
  
  void CXFA_Node::ResetData() {
    WideString wsValue;
    switch (GetUIType()) {
      case XFA_Element::ImageEdit: {
        CXFA_Value* imageValue = GetDefaultValueIfExists();
        CXFA_Image* image = imageValue ? imageValue->GetImageIfExists() : nullptr;
        WideString wsContentType, wsHref;
        if (image) {
          wsValue = image->GetContent();
          wsContentType = image->GetContentType();
          wsHref = image->GetHref();
        }
        SetImageEdit(wsContentType, wsHref, wsValue);
        break;
      }
      case XFA_Element::ExclGroup: {
        CXFA_Node* pNextChild = GetFirstContainerChild();
        while (pNextChild) {
          CXFA_Node* pChild = pNextChild;
          if (!pChild->IsWidgetReady())
            continue;
  
          bool done = false;
          if (wsValue.IsEmpty()) {
            CXFA_Value* defValue = pChild->GetDefaultValueIfExists();
            if (defValue) {
              wsValue = defValue->GetChildValueContent();
              SetValue(XFA_VALUEPICTURE_Raw, wsValue);
              pChild->SetValue(XFA_VALUEPICTURE_Raw, wsValue);
              done = true;
            }
          }
          if (!done) {
            CXFA_Items* pItems =
                pChild->GetChild<CXFA_Items>(0, XFA_Element::Items, false);
            if (!pItems)
              continue;
  
            WideString itemText;
            if (pItems->CountChildren(XFA_Element::Unknown, false) > 1) {
              itemText =
                  pItems->GetChild<CXFA_Node>(1, XFA_Element::Unknown, false)
                      ->JSObject()
                      ->GetContent(false);
            }
            pChild->SetValue(XFA_VALUEPICTURE_Raw, itemText);
          }
          pNextChild = pChild->GetNextContainerSibling();
        }
        break;
      }
      case XFA_Element::ChoiceList:
        ClearAllSelections();
      default: {
        CXFA_Value* defValue = GetDefaultValueIfExists();
        if (defValue)
          wsValue = defValue->GetChildValueContent();
  
        SetValue(XFA_VALUEPICTURE_Raw, wsValue);
        break;
      }
    }
  }
  
  void CXFA_Node::SetImageEdit(const WideString& wsContentType,
                               const WideString& wsHref,
                               const WideString& wsData) {
    CXFA_Value* formValue = GetFormValueIfExists();
    CXFA_Image* image = formValue ? formValue->GetImageIfExists() : nullptr;
    if (image) {
      image->SetContentType(WideString(wsContentType));
      image->SetHref(wsHref);
    }
  
    JSObject()->SetContent(wsData, GetFormatDataValue(wsData), true, false, true);
  
    CXFA_Node* pBind = GetBindData();
    if (!pBind) {
      if (image)
        image->SetTransferEncoding(XFA_AttributeEnum::Base64);
      return;
    }
    pBind->JSObject()->SetCData(XFA_Attribute::ContentType, wsContentType, false,
                                false);
    CXFA_Node* pHrefNode = pBind->GetFirstChild();
    if (pHrefNode) {
      pHrefNode->JSObject()->SetCData(XFA_Attribute::Value, wsHref, false, false);
    } else {
      CFX_XMLNode* pXMLNode = pBind->GetXMLMappingNode();
      ASSERT(pXMLNode && pXMLNode->GetType() == FX_XMLNODE_Element);
      static_cast<CFX_XMLElement*>(pXMLNode)->SetString(L"href", wsHref);
    }
  }
  
  CXFA_FFWidget* CXFA_Node::GetNextWidget(CXFA_FFWidget* pWidget) {
    return static_cast<CXFA_FFWidget*>(pWidget->GetNext());
  }
  
  void CXFA_Node::UpdateUIDisplay(CXFA_FFDocView* docView,
                                  CXFA_FFWidget* pExcept) {
    CXFA_FFWidget* pWidget = docView->GetWidgetForNode(this);
    for (; pWidget; pWidget = GetNextWidget(pWidget)) {
      if (pWidget == pExcept || !pWidget->IsLoaded() ||
          (GetUIType() != XFA_Element::CheckButton && pWidget->IsFocused())) {
        continue;
      }
      pWidget->UpdateFWLData();
      pWidget->AddInvalidateRect();
    }
  }
  
  void CXFA_Node::CalcCaptionSize(CXFA_FFDoc* doc, CFX_SizeF& szCap) {
    CXFA_Caption* caption = GetCaptionIfExists();
    if (!caption || !caption->IsVisible())
      return;
  
    LoadCaption(doc);
  
    XFA_Element eUIType = GetUIType();
    XFA_AttributeEnum iCapPlacement = caption->GetPlacementType();
    float fCapReserve = caption->GetReserve();
    const bool bVert = iCapPlacement == XFA_AttributeEnum::Top ||
                       iCapPlacement == XFA_AttributeEnum::Bottom;
    const bool bReserveExit = fCapReserve > 0.01;
    CXFA_TextLayout* pCapTextLayout =
        static_cast<CXFA_FieldLayoutData*>(m_pLayoutData.get())
            ->m_pCapTextLayout.get();
    if (pCapTextLayout) {
      if (!bVert && eUIType != XFA_Element::Button)
        szCap.width = fCapReserve;
  
      CFX_SizeF minSize;
      szCap = pCapTextLayout->CalcSize(minSize, szCap);
      if (bReserveExit)
        bVert ? szCap.height = fCapReserve : szCap.width = fCapReserve;
    } else {
      float fFontSize = 10.0f;
      CXFA_Font* font = caption->GetFontIfExists();
      if (font) {
        fFontSize = font->GetFontSize();
      } else {
        CXFA_Font* widgetfont = GetFontIfExists();
        if (widgetfont)
          fFontSize = widgetfont->GetFontSize();
      }
  
      if (bVert) {
        szCap.height = fCapReserve > 0 ? fCapReserve : fFontSize;
      } else {
        szCap.width = fCapReserve > 0 ? fCapReserve : 0;
        szCap.height = fFontSize;
      }
    }
  
    CXFA_Margin* captionMargin = caption->GetMarginIfExists();
    if (!captionMargin)
      return;
  
    float fLeftInset = captionMargin->GetLeftInset();
    float fTopInset = captionMargin->GetTopInset();
    float fRightInset = captionMargin->GetRightInset();
    float fBottomInset = captionMargin->GetBottomInset();
    if (bReserveExit) {
      bVert ? (szCap.width += fLeftInset + fRightInset)
            : (szCap.height += fTopInset + fBottomInset);
    } else {
      szCap.width += fLeftInset + fRightInset;
      szCap.height += fTopInset + fBottomInset;
    }
  }
  
  bool CXFA_Node::CalculateFieldAutoSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    CFX_SizeF szCap;
    CalcCaptionSize(doc, szCap);
  
    CFX_RectF rtUIMargin = GetUIMargin();
    size.width += rtUIMargin.left + rtUIMargin.width;
    size.height += rtUIMargin.top + rtUIMargin.height;
    if (szCap.width > 0 && szCap.height > 0) {
      CXFA_Caption* caption = GetCaptionIfExists();
      XFA_AttributeEnum placement = caption ? caption->GetPlacementType()
                                            : CXFA_Caption::kDefaultPlacementType;
      switch (placement) {
        case XFA_AttributeEnum::Left:
        case XFA_AttributeEnum::Right:
        case XFA_AttributeEnum::Inline: {
          size.width += szCap.width;
          size.height = std::max(size.height, szCap.height);
        } break;
        case XFA_AttributeEnum::Top:
        case XFA_AttributeEnum::Bottom: {
          size.height += szCap.height;
          size.width = std::max(size.width, szCap.width);
        }
        default:
          break;
      }
    }
    return CalculateWidgetAutoSize(size);
  }
  
  bool CXFA_Node::CalculateWidgetAutoSize(CFX_SizeF& size) {
    CXFA_Margin* margin = GetMarginIfExists();
    if (margin) {
      size.width += margin->GetLeftInset() + margin->GetRightInset();
      size.height += margin->GetTopInset() + margin->GetBottomInset();
    }
  
    CXFA_Para* para = GetParaIfExists();
    if (para)
      size.width += para->GetMarginLeft() + para->GetTextIndent();
  
    Optional<float> width = TryWidth();
    if (width) {
      size.width = *width;
    } else {
      Optional<float> min = TryMinWidth();
      if (min)
        size.width = std::max(size.width, *min);
  
      Optional<float> max = TryMaxWidth();
      if (max && *max > 0)
        size.width = std::min(size.width, *max);
    }
  
    Optional<float> height = TryHeight();
    if (height) {
      size.height = *height;
    } else {
      Optional<float> min = TryMinHeight();
      if (min)
        size.height = std::max(size.height, *min);
  
      Optional<float> max = TryMaxHeight();
      if (max && *max > 0)
        size.height = std::min(size.height, *max);
    }
    return true;
  }
  
  void CXFA_Node::CalculateTextContentSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    float fFontSize = GetFontSize();
    WideString wsText = GetValue(XFA_VALUEPICTURE_Display);
    if (wsText.IsEmpty()) {
      size.height += fFontSize;
      return;
    }
  
    wchar_t wcEnter = '\n';
    wchar_t wsLast = wsText[wsText.GetLength() - 1];
    if (wsLast == wcEnter)
      wsText = wsText + wcEnter;
  
    CXFA_FieldLayoutData* layoutData =
        static_cast<CXFA_FieldLayoutData*>(m_pLayoutData.get());
    if (!layoutData->m_pTextOut) {
      layoutData->m_pTextOut = pdfium::MakeUnique<CFDE_TextOut>();
      CFDE_TextOut* pTextOut = layoutData->m_pTextOut.get();
      pTextOut->SetFont(GetFDEFont(doc));
      pTextOut->SetFontSize(fFontSize);
      pTextOut->SetLineBreakTolerance(fFontSize * 0.2f);
      pTextOut->SetLineSpace(GetLineHeight());
  
      FDE_TextStyle dwStyles;
      dwStyles.last_line_height_ = true;
      if (GetUIType() == XFA_Element::TextEdit && IsMultiLine())
        dwStyles.line_wrap_ = true;
  
      pTextOut->SetStyles(dwStyles);
    }
    layoutData->m_pTextOut->CalcLogicSize(wsText, size);
  }
  
  bool CXFA_Node::CalculateTextEditAutoSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    if (size.width > 0) {
      CFX_SizeF szOrz = size;
      CFX_SizeF szCap;
      CalcCaptionSize(doc, szCap);
      bool bCapExit = szCap.width > 0.01 && szCap.height > 0.01;
      XFA_AttributeEnum iCapPlacement = XFA_AttributeEnum::Unknown;
      if (bCapExit) {
        CXFA_Caption* caption = GetCaptionIfExists();
        iCapPlacement = caption ? caption->GetPlacementType()
                                : CXFA_Caption::kDefaultPlacementType;
        switch (iCapPlacement) {
          case XFA_AttributeEnum::Left:
          case XFA_AttributeEnum::Right:
          case XFA_AttributeEnum::Inline: {
            size.width -= szCap.width;
          }
          default:
            break;
        }
      }
      CFX_RectF rtUIMargin = GetUIMargin();
      size.width -= rtUIMargin.left + rtUIMargin.width;
      CXFA_Margin* margin = GetMarginIfExists();
      if (margin)
        size.width -= margin->GetLeftInset() + margin->GetRightInset();
  
      CalculateTextContentSize(doc, size);
      size.height += rtUIMargin.top + rtUIMargin.height;
      if (bCapExit) {
        switch (iCapPlacement) {
          case XFA_AttributeEnum::Left:
          case XFA_AttributeEnum::Right:
          case XFA_AttributeEnum::Inline: {
            size.height = std::max(size.height, szCap.height);
          } break;
          case XFA_AttributeEnum::Top:
          case XFA_AttributeEnum::Bottom: {
            size.height += szCap.height;
          }
          default:
            break;
        }
      }
      size.width = szOrz.width;
      return CalculateWidgetAutoSize(size);
    }
    CalculateTextContentSize(doc, size);
    return CalculateFieldAutoSize(doc, size);
  }
  
  bool CXFA_Node::CalculateCheckButtonAutoSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    float fCheckSize = GetCheckButtonSize();
    size = CFX_SizeF(fCheckSize, fCheckSize);
    return CalculateFieldAutoSize(doc, size);
  }
  
  bool CXFA_Node::CalculatePushButtonAutoSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    CalcCaptionSize(doc, size);
    return CalculateWidgetAutoSize(size);
  }
  
  CFX_SizeF CXFA_Node::CalculateImageSize(float img_width,
                                          float img_height,
                                          float dpi_x,
                                          float dpi_y) {
    CFX_RectF rtImage(0, 0, XFA_UnitPx2Pt(img_width, dpi_x),
                      XFA_UnitPx2Pt(img_height, dpi_y));
  
    CFX_RectF rtFit;
    Optional<float> width = TryWidth();
    if (width) {
      rtFit.width = *width;
      GetWidthWithoutMargin(rtFit.width);
    } else {
      rtFit.width = rtImage.width;
    }
  
    Optional<float> height = TryHeight();
    if (height) {
      rtFit.height = *height;
      GetHeightWithoutMargin(rtFit.height);
    } else {
      rtFit.height = rtImage.height;
    }
  
    return rtFit.Size();
  }
  
  bool CXFA_Node::CalculateImageAutoSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    if (!GetImageImage())
      LoadImageImage(doc);
  
    size.clear();
    RetainPtr<CFX_DIBitmap> pBitmap = GetImageImage();
    if (!pBitmap)
      return CalculateWidgetAutoSize(size);
  
    int32_t iImageXDpi = 0;
    int32_t iImageYDpi = 0;
    GetImageDpi(iImageXDpi, iImageYDpi);
  
    size = CalculateImageSize(pBitmap->GetWidth(), pBitmap->GetHeight(),
                              iImageXDpi, iImageYDpi);
    return CalculateWidgetAutoSize(size);
  }
  
  bool CXFA_Node::CalculateImageEditAutoSize(CXFA_FFDoc* doc, CFX_SizeF& size) {
    if (!GetImageEditImage())
      LoadImageEditImage(doc);
  
    size.clear();
    RetainPtr<CFX_DIBitmap> pBitmap = GetImageEditImage();
    if (!pBitmap)
      return CalculateFieldAutoSize(doc, size);
  
    int32_t iImageXDpi = 0;
    int32_t iImageYDpi = 0;
    GetImageEditDpi(iImageXDpi, iImageYDpi);
  
    size = CalculateImageSize(pBitmap->GetWidth(), pBitmap->GetHeight(),
                              iImageXDpi, iImageYDpi);
    return CalculateFieldAutoSize(doc, size);
  }
  
  bool CXFA_Node::LoadImageImage(CXFA_FFDoc* doc) {
    InitLayoutData();
    return static_cast<CXFA_ImageLayoutData*>(m_pLayoutData.get())
        ->LoadImageData(doc, this);
  }
  
  bool CXFA_Node::LoadImageEditImage(CXFA_FFDoc* doc) {
    InitLayoutData();
    return static_cast<CXFA_ImageEditData*>(m_pLayoutData.get())
        ->LoadImageData(doc, this);
  }
  
  void CXFA_Node::GetImageDpi(int32_t& iImageXDpi, int32_t& iImageYDpi) {
    CXFA_ImageLayoutData* pData =
        static_cast<CXFA_ImageLayoutData*>(m_pLayoutData.get());
    iImageXDpi = pData->m_iImageXDpi;
    iImageYDpi = pData->m_iImageYDpi;
  }
  
  void CXFA_Node::GetImageEditDpi(int32_t& iImageXDpi, int32_t& iImageYDpi) {
    CXFA_ImageEditData* pData =
        static_cast<CXFA_ImageEditData*>(m_pLayoutData.get());
    iImageXDpi = pData->m_iImageXDpi;
    iImageYDpi = pData->m_iImageYDpi;
  }
  
  void CXFA_Node::LoadText(CXFA_FFDoc* doc) {
    InitLayoutData();
    static_cast<CXFA_TextLayoutData*>(m_pLayoutData.get())->LoadText(doc, this);
  }
  
  float CXFA_Node::CalculateWidgetAutoWidth(float fWidthCalc) {
    CXFA_Margin* margin = GetMarginIfExists();
    if (margin)
      fWidthCalc += margin->GetLeftInset() + margin->GetRightInset();
  
    Optional<float> min = TryMinWidth();
    if (min)
      fWidthCalc = std::max(fWidthCalc, *min);
  
    Optional<float> max = TryMaxWidth();
    if (max && *max > 0)
      fWidthCalc = std::min(fWidthCalc, *max);
  
    return fWidthCalc;
  }
  
  float CXFA_Node::GetWidthWithoutMargin(float fWidthCalc) {
    CXFA_Margin* margin = GetMarginIfExists();
    if (margin)
      fWidthCalc -= margin->GetLeftInset() + margin->GetRightInset();
    return fWidthCalc;
  }
  
  float CXFA_Node::CalculateWidgetAutoHeight(float fHeightCalc) {
    CXFA_Margin* margin = GetMarginIfExists();
    if (margin)
      fHeightCalc += margin->GetTopInset() + margin->GetBottomInset();
  
    Optional<float> min = TryMinHeight();
    if (min)
      fHeightCalc = std::max(fHeightCalc, *min);
  
    Optional<float> max = TryMaxHeight();
    if (max && *max > 0)
      fHeightCalc = std::min(fHeightCalc, *max);
  
    return fHeightCalc;
  }
  
  float CXFA_Node::GetHeightWithoutMargin(float fHeightCalc) {
    CXFA_Margin* margin = GetMarginIfExists();
    if (margin)
      fHeightCalc -= margin->GetTopInset() + margin->GetBottomInset();
    return fHeightCalc;
  }
  
  void CXFA_Node::StartWidgetLayout(CXFA_FFDoc* doc,
                                    float& fCalcWidth,
                                    float& fCalcHeight) {
    InitLayoutData();
  
    XFA_Element eUIType = GetUIType();
    if (eUIType == XFA_Element::Text) {
      m_pLayoutData->m_fWidgetHeight = TryHeight().value_or(-1);
      StartTextLayout(doc, fCalcWidth, fCalcHeight);
      return;
    }
    if (fCalcWidth > 0 && fCalcHeight > 0)
      return;
  
    m_pLayoutData->m_fWidgetHeight = -1;
    float fWidth = 0;
    if (fCalcWidth > 0 && fCalcHeight < 0) {
      Optional<float> height = TryHeight();
      if (height)
        fCalcHeight = *height;
      else
        CalculateAccWidthAndHeight(doc, eUIType, fCalcWidth, fCalcHeight);
  
      m_pLayoutData->m_fWidgetHeight = fCalcHeight;
      return;
    }
    if (fCalcWidth < 0 && fCalcHeight < 0) {
      Optional<float> height;
      Optional<float> width = TryWidth();
      if (width) {
        fWidth = *width;
  
        height = TryHeight();
        if (height)
          fCalcHeight = *height;
      }
      if (!width || !height)
        CalculateAccWidthAndHeight(doc, eUIType, fWidth, fCalcHeight);
  
      fCalcWidth = fWidth;
    }
    m_pLayoutData->m_fWidgetHeight = fCalcHeight;
  }
  
  void CXFA_Node::CalculateAccWidthAndHeight(CXFA_FFDoc* doc,
                                             XFA_Element eUIType,
                                             float& fWidth,
                                             float& fCalcHeight) {
    CFX_SizeF sz(fWidth, m_pLayoutData->m_fWidgetHeight);
    switch (eUIType) {
      case XFA_Element::Barcode:
      case XFA_Element::ChoiceList:
      case XFA_Element::Signature:
        CalculateFieldAutoSize(doc, sz);
        break;
      case XFA_Element::ImageEdit:
        CalculateImageEditAutoSize(doc, sz);
        break;
      case XFA_Element::Button:
        CalculatePushButtonAutoSize(doc, sz);
        break;
      case XFA_Element::CheckButton:
        CalculateCheckButtonAutoSize(doc, sz);
        break;
      case XFA_Element::DateTimeEdit:
      case XFA_Element::NumericEdit:
      case XFA_Element::PasswordEdit:
      case XFA_Element::TextEdit:
        CalculateTextEditAutoSize(doc, sz);
        break;
      case XFA_Element::Image:
        CalculateImageAutoSize(doc, sz);
        break;
      case XFA_Element::Arc:
      case XFA_Element::Line:
      case XFA_Element::Rectangle:
      case XFA_Element::Subform:
      case XFA_Element::ExclGroup:
        CalculateWidgetAutoSize(sz);
        break;
      default:
        break;
    }
    fWidth = sz.width;
    m_pLayoutData->m_fWidgetHeight = sz.height;
    fCalcHeight = sz.height;
  }
  
  bool CXFA_Node::FindSplitPos(CXFA_FFDocView* docView,
                               int32_t iBlockIndex,
                               float& fCalcHeight) {
    XFA_Element eUIType = GetUIType();
    if (eUIType == XFA_Element::Subform)
      return false;
  
    if (eUIType != XFA_Element::Text && eUIType != XFA_Element::TextEdit &&
        eUIType != XFA_Element::NumericEdit &&
        eUIType != XFA_Element::PasswordEdit) {
      fCalcHeight = 0;
      return true;
    }
  
    float fTopInset = 0;
    float fBottomInset = 0;
    if (iBlockIndex == 0) {
      CXFA_Margin* margin = GetMarginIfExists();
      if (margin) {
        fTopInset = margin->GetTopInset();
        fBottomInset = margin->GetBottomInset();
      }
  
      CFX_RectF rtUIMargin = GetUIMargin();
      fTopInset += rtUIMargin.top;
      fBottomInset += rtUIMargin.width;
    }
    if (eUIType == XFA_Element::Text) {
      float fHeight = fCalcHeight;
      if (iBlockIndex == 0) {
        fCalcHeight = fCalcHeight - fTopInset;
        if (fCalcHeight < 0)
          fCalcHeight = 0;
      }
  
      CXFA_TextLayout* pTextLayout =
          static_cast<CXFA_TextLayoutData*>(m_pLayoutData.get())->GetTextLayout();
      fCalcHeight =
          pTextLayout->DoLayout(iBlockIndex, fCalcHeight, fCalcHeight,
                                m_pLayoutData->m_fWidgetHeight - fTopInset);
      if (fCalcHeight != 0) {
        if (iBlockIndex == 0)
          fCalcHeight = fCalcHeight + fTopInset;
        if (fabs(fHeight - fCalcHeight) < XFA_FLOAT_PERCISION)
          return false;
      }
      return true;
    }
    XFA_AttributeEnum iCapPlacement = XFA_AttributeEnum::Unknown;
    float fCapReserve = 0;
    if (iBlockIndex == 0) {
      CXFA_Caption* caption = GetCaptionIfExists();
      if (caption && !caption->IsHidden()) {
        iCapPlacement = caption->GetPlacementType();
        fCapReserve = caption->GetReserve();
      }
      if (iCapPlacement == XFA_AttributeEnum::Top &&
          fCalcHeight < fCapReserve + fTopInset) {
        fCalcHeight = 0;
        return true;
      }
      if (iCapPlacement == XFA_AttributeEnum::Bottom &&
          m_pLayoutData->m_fWidgetHeight - fCapReserve - fBottomInset) {
        fCalcHeight = 0;
        return true;
      }
      if (iCapPlacement != XFA_AttributeEnum::Top)
        fCapReserve = 0;
    }
    CXFA_FieldLayoutData* pFieldData =
        static_cast<CXFA_FieldLayoutData*>(m_pLayoutData.get());
    int32_t iLinesCount = 0;
    float fHeight = m_pLayoutData->m_fWidgetHeight;
    if (GetValue(XFA_VALUEPICTURE_Display).IsEmpty()) {
      iLinesCount = 1;
    } else {
      if (!pFieldData->m_pTextOut) {
        // TODO(dsinclair): Inline fWidth when the 2nd param of
        // CalculateAccWidthAndHeight isn't a ref-param.
        float fWidth = TryWidth().value_or(0);
        CalculateAccWidthAndHeight(docView->GetDoc(), eUIType, fWidth, fHeight);
      }
      iLinesCount = pFieldData->m_pTextOut->GetTotalLines();
    }
    std::vector<float>* pFieldArray = &pFieldData->m_FieldSplitArray;
    int32_t iFieldSplitCount = pdfium::CollectionSize<int32_t>(*pFieldArray);
    for (int32_t i = 0; i < iBlockIndex * 3; i += 3) {
      iLinesCount -= (int32_t)(*pFieldArray)[i + 1];
      fHeight -= (*pFieldArray)[i + 2];
    }
    if (iLinesCount == 0)
      return false;
  
    float fLineHeight = GetLineHeight();
    float fFontSize = GetFontSize();
    float fTextHeight = iLinesCount * fLineHeight - fLineHeight + fFontSize;
    float fSpaceAbove = 0;
    float fStartOffset = 0;
    if (fHeight > 0.1f && iBlockIndex == 0) {
      fStartOffset = fTopInset;
      fHeight -= (fTopInset + fBottomInset);
      CXFA_Para* para = GetParaIfExists();
      if (para) {
        fSpaceAbove = para->GetSpaceAbove();
        float fSpaceBelow = para->GetSpaceBelow();
        fHeight -= (fSpaceAbove + fSpaceBelow);
        switch (para->GetVerticalAlign()) {
          case XFA_AttributeEnum::Top:
            fStartOffset += fSpaceAbove;
            break;
          case XFA_AttributeEnum::Middle:
            fStartOffset += ((fHeight - fTextHeight) / 2 + fSpaceAbove);
            break;
          case XFA_AttributeEnum::Bottom:
            fStartOffset += (fHeight - fTextHeight + fSpaceAbove);
            break;
          default:
            NOTREACHED();
            break;
        }
      }
      if (fStartOffset < 0.1f)
        fStartOffset = 0;
    }
    for (int32_t i = iBlockIndex - 1; iBlockIndex > 0 && i < iBlockIndex; i++) {
      fStartOffset = (*pFieldArray)[i * 3] - (*pFieldArray)[i * 3 + 2];
      if (fStartOffset < 0.1f)
        fStartOffset = 0;
    }
    if (iFieldSplitCount / 3 == (iBlockIndex + 1))
      (*pFieldArray)[0] = fStartOffset;
    else
      pFieldArray->push_back(fStartOffset);
  
    XFA_VERSION version = docView->GetDoc()->GetXFADoc()->GetCurVersionMode();
    bool bCanSplitNoContent = false;
    XFA_AttributeEnum eLayoutMode = GetParent()
                                        ->JSObject()
                                        ->TryEnum(XFA_Attribute::Layout, true)
                                        .value_or(XFA_AttributeEnum::Position);
    if ((eLayoutMode == XFA_AttributeEnum::Position ||
         eLayoutMode == XFA_AttributeEnum::Tb ||
         eLayoutMode == XFA_AttributeEnum::Row ||
         eLayoutMode == XFA_AttributeEnum::Table) &&
        version > XFA_VERSION_208) {
      bCanSplitNoContent = true;
    }
    if ((eLayoutMode == XFA_AttributeEnum::Tb ||
         eLayoutMode == XFA_AttributeEnum::Row ||
         eLayoutMode == XFA_AttributeEnum::Table) &&
        version <= XFA_VERSION_208) {
      if (fStartOffset < fCalcHeight) {
        bCanSplitNoContent = true;
      } else {
        fCalcHeight = 0;
        return true;
      }
    }
    if (bCanSplitNoContent) {
      if ((fCalcHeight - fTopInset - fSpaceAbove < fLineHeight)) {
        fCalcHeight = 0;
        return true;
      }
      if (fStartOffset + XFA_FLOAT_PERCISION >= fCalcHeight) {
        if (iFieldSplitCount / 3 == (iBlockIndex + 1)) {
          (*pFieldArray)[iBlockIndex * 3 + 1] = 0;
          (*pFieldArray)[iBlockIndex * 3 + 2] = fCalcHeight;
        } else {
          pFieldArray->push_back(0);
          pFieldArray->push_back(fCalcHeight);
        }
        return false;
      }
      if (fCalcHeight - fStartOffset < fLineHeight) {
        fCalcHeight = fStartOffset;
        if (iFieldSplitCount / 3 == (iBlockIndex + 1)) {
          (*pFieldArray)[iBlockIndex * 3 + 1] = 0;
          (*pFieldArray)[iBlockIndex * 3 + 2] = fCalcHeight;
        } else {
          pFieldArray->push_back(0);
          pFieldArray->push_back(fCalcHeight);
        }
        return true;
      }
      float fTextNum =
          fCalcHeight + XFA_FLOAT_PERCISION - fCapReserve - fStartOffset;
      int32_t iLineNum =
          (int32_t)((fTextNum + (fLineHeight - fFontSize)) / fLineHeight);
      if (iLineNum >= iLinesCount) {
        if (fCalcHeight - fStartOffset - fTextHeight >= fFontSize) {
          if (iFieldSplitCount / 3 == (iBlockIndex + 1)) {
            (*pFieldArray)[iBlockIndex * 3 + 1] = (float)iLinesCount;
            (*pFieldArray)[iBlockIndex * 3 + 2] = fCalcHeight;
          } else {
            pFieldArray->push_back((float)iLinesCount);
            pFieldArray->push_back(fCalcHeight);
          }
          return false;
        }
        if (fHeight - fStartOffset - fTextHeight < fFontSize) {
          iLineNum -= 1;
          if (iLineNum == 0) {
            fCalcHeight = 0;
            return true;
          }
        } else {
          iLineNum = (int32_t)(fTextNum / fLineHeight);
        }
      }
      if (iLineNum > 0) {
        float fSplitHeight = iLineNum * fLineHeight + fCapReserve + fStartOffset;
        if (iFieldSplitCount / 3 == (iBlockIndex + 1)) {
          (*pFieldArray)[iBlockIndex * 3 + 1] = (float)iLineNum;
          (*pFieldArray)[iBlockIndex * 3 + 2] = fSplitHeight;
        } else {
          pFieldArray->push_back((float)iLineNum);
          pFieldArray->push_back(fSplitHeight);
        }
        if (fabs(fSplitHeight - fCalcHeight) < XFA_FLOAT_PERCISION)
          return false;
  
        fCalcHeight = fSplitHeight;
        return true;
      }
    }
    fCalcHeight = 0;
    return true;
  }
  
  void CXFA_Node::InitLayoutData() {
    if (m_pLayoutData)
      return;
  
    switch (GetUIType()) {
      case XFA_Element::Text:
        m_pLayoutData = pdfium::MakeUnique<CXFA_TextLayoutData>();
        return;
      case XFA_Element::TextEdit:
        m_pLayoutData = pdfium::MakeUnique<CXFA_TextEditData>();
        return;
      case XFA_Element::Image:
        m_pLayoutData = pdfium::MakeUnique<CXFA_ImageLayoutData>();
        return;
      case XFA_Element::ImageEdit:
        m_pLayoutData = pdfium::MakeUnique<CXFA_ImageEditData>();
        return;
      default:
        break;
    }
    if (GetElementType() == XFA_Element::Field) {
      m_pLayoutData = pdfium::MakeUnique<CXFA_FieldLayoutData>();
      return;
    }
    m_pLayoutData = pdfium::MakeUnique<CXFA_WidgetLayoutData>();
  }
  
  void CXFA_Node::StartTextLayout(CXFA_FFDoc* doc,
                                  float& fCalcWidth,
                                  float& fCalcHeight) {
    LoadText(doc);
  
    CXFA_TextLayout* pTextLayout =
        static_cast<CXFA_TextLayoutData*>(m_pLayoutData.get())->GetTextLayout();
    float fTextHeight = 0;
    if (fCalcWidth > 0 && fCalcHeight > 0) {
      float fWidth = GetWidthWithoutMargin(fCalcWidth);
      pTextLayout->StartLayout(fWidth);
      fTextHeight = fCalcHeight;
      fTextHeight = GetHeightWithoutMargin(fTextHeight);
      pTextLayout->DoLayout(0, fTextHeight, -1, fTextHeight);
      return;
    }
    if (fCalcWidth > 0 && fCalcHeight < 0) {
      float fWidth = GetWidthWithoutMargin(fCalcWidth);
      pTextLayout->StartLayout(fWidth);
    }
  
    if (fCalcWidth < 0 && fCalcHeight < 0) {
      Optional<float> width = TryWidth();
      if (width) {
        pTextLayout->StartLayout(GetWidthWithoutMargin(*width));
        fCalcWidth = *width;
      } else {
        float fMaxWidth = CalculateWidgetAutoWidth(pTextLayout->StartLayout(-1));
        pTextLayout->StartLayout(GetWidthWithoutMargin(fMaxWidth));
        fCalcWidth = fMaxWidth;
      }
    }
  
    if (m_pLayoutData->m_fWidgetHeight < 0) {
      m_pLayoutData->m_fWidgetHeight = pTextLayout->GetLayoutHeight();
      m_pLayoutData->m_fWidgetHeight =
          CalculateWidgetAutoHeight(m_pLayoutData->m_fWidgetHeight);
    }
    fTextHeight = m_pLayoutData->m_fWidgetHeight;
    fTextHeight = GetHeightWithoutMargin(fTextHeight);
    pTextLayout->DoLayout(0, fTextHeight, -1, fTextHeight);
    fCalcHeight = m_pLayoutData->m_fWidgetHeight;
  }
  
  bool CXFA_Node::LoadCaption(CXFA_FFDoc* doc) {
    InitLayoutData();
    return static_cast<CXFA_FieldLayoutData*>(m_pLayoutData.get())
        ->LoadCaption(doc, this);
  }
  
  CXFA_TextLayout* CXFA_Node::GetCaptionTextLayout() {
    return m_pLayoutData ? static_cast<CXFA_FieldLayoutData*>(m_pLayoutData.get())
                               ->m_pCapTextLayout.get()
                         : nullptr;
  }
  
  CXFA_TextLayout* CXFA_Node::GetTextLayout() {
    return m_pLayoutData ? static_cast<CXFA_TextLayoutData*>(m_pLayoutData.get())
                               ->GetTextLayout()
                         : nullptr;
  }
  
  RetainPtr<CFX_DIBitmap> CXFA_Node::GetImageImage() {
    return m_pLayoutData ? static_cast<CXFA_ImageLayoutData*>(m_pLayoutData.get())
                               ->m_pDIBitmap
                         : nullptr;
  }
  
  RetainPtr<CFX_DIBitmap> CXFA_Node::GetImageEditImage() {
    return m_pLayoutData ? static_cast<CXFA_ImageEditData*>(m_pLayoutData.get())
                               ->m_pDIBitmap
                         : nullptr;
  }
  
  void CXFA_Node::SetImageImage(const RetainPtr<CFX_DIBitmap>& newImage) {
    CXFA_ImageLayoutData* pData =
        static_cast<CXFA_ImageLayoutData*>(m_pLayoutData.get());
    if (pData->m_pDIBitmap != newImage)
      pData->m_pDIBitmap = newImage;
  }
  
  void CXFA_Node::SetImageEditImage(const RetainPtr<CFX_DIBitmap>& newImage) {
    CXFA_ImageEditData* pData =
        static_cast<CXFA_ImageEditData*>(m_pLayoutData.get());
    if (pData->m_pDIBitmap != newImage)
      pData->m_pDIBitmap = newImage;
  }
  
  RetainPtr<CFGAS_GEFont> CXFA_Node::GetFDEFont(CXFA_FFDoc* doc) {
    WideString wsFontName = L"Courier";
    uint32_t dwFontStyle = 0;
    CXFA_Font* font = GetFontIfExists();
    if (font) {
      if (font->IsBold())
        dwFontStyle |= FXFONT_BOLD;
      if (font->IsItalic())
        dwFontStyle |= FXFONT_ITALIC;
  
      wsFontName = font->GetTypeface();
    }
    return doc->GetApp()->GetXFAFontMgr()->GetFont(doc, wsFontName.AsStringView(),
                                                   dwFontStyle);
  }
  
  XFA_AttributeEnum CXFA_Node::GetButtonHighlight() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild)
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::Highlight);
    return XFA_AttributeEnum::Inverted;
  }
  
  bool CXFA_Node::HasButtonRollover() {
    CXFA_Items* pItems = GetChild<CXFA_Items>(0, XFA_Element::Items, false);
    if (!pItems)
      return false;
  
    for (CXFA_Node* pText = pItems->GetFirstChild(); pText;
         pText = pText->GetNextSibling()) {
      if (pText->JSObject()->GetCData(XFA_Attribute::Name) == L"rollover")
        return !pText->JSObject()->GetContent(false).IsEmpty();
    }
    return false;
  }
  
  bool CXFA_Node::HasButtonDown() {
    CXFA_Items* pItems = GetChild<CXFA_Items>(0, XFA_Element::Items, false);
    if (!pItems)
      return false;
  
    for (CXFA_Node* pText = pItems->GetFirstChild(); pText;
         pText = pText->GetNextSibling()) {
      if (pText->JSObject()->GetCData(XFA_Attribute::Name) == L"down")
        return !pText->JSObject()->GetContent(false).IsEmpty();
    }
    return false;
  }
  
  bool CXFA_Node::IsCheckButtonRound() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild)
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::Shape) ==
             XFA_AttributeEnum::Round;
    return false;
  }
  
  XFA_AttributeEnum CXFA_Node::GetCheckButtonMark() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild)
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::Mark);
    return XFA_AttributeEnum::Default;
  }
  
  bool CXFA_Node::IsRadioButton() {
    CXFA_Node* pParent = GetParent();
    return pParent && pParent->GetElementType() == XFA_Element::ExclGroup;
  }
  
  float CXFA_Node::GetCheckButtonSize() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild) {
      return pUIChild->JSObject()
          ->GetMeasure(XFA_Attribute::Size)
          .ToUnit(XFA_Unit::Pt);
    }
    return CXFA_Measurement(10, XFA_Unit::Pt).ToUnit(XFA_Unit::Pt);
  }
  
  bool CXFA_Node::IsAllowNeutral() {
    CXFA_Node* pUIChild = GetUIChild();
    return pUIChild &&
           pUIChild->JSObject()->GetBoolean(XFA_Attribute::AllowNeutral);
  }
  
  XFA_CHECKSTATE CXFA_Node::GetCheckState() {
    WideString wsValue = GetRawValue();
    if (wsValue.IsEmpty())
      return XFA_CHECKSTATE_Off;
  
    auto* pItems = GetChild<CXFA_Items>(0, XFA_Element::Items, false);
    if (!pItems)
      return XFA_CHECKSTATE_Off;
  
    CXFA_Node* pText = pItems->GetFirstChild();
    int32_t i = 0;
    while (pText) {
      Optional<WideString> wsContent = pText->JSObject()->TryContent(false, true);
      if (wsContent && *wsContent == wsValue)
        return static_cast<XFA_CHECKSTATE>(i);
  
      i++;
      pText = pText->GetNextSibling();
    }
    return XFA_CHECKSTATE_Off;
  }
  
  void CXFA_Node::SetCheckState(XFA_CHECKSTATE eCheckState, bool bNotify) {
    CXFA_Node* node = GetExclGroupIfExists();
    if (!node) {
      CXFA_Items* pItems = GetChild<CXFA_Items>(0, XFA_Element::Items, false);
      if (!pItems)
        return;
  
      int32_t i = -1;
      CXFA_Node* pText = pItems->GetFirstChild();
      WideString wsContent;
      while (pText) {
        i++;
        if (i == eCheckState) {
          wsContent = pText->JSObject()->GetContent(false);
          break;
        }
        pText = pText->GetNextSibling();
      }
      SyncValue(wsContent, bNotify);
  
      return;
    }
  
    WideString wsValue;
    if (eCheckState != XFA_CHECKSTATE_Off) {
      if (CXFA_Items* pItems =
              GetChild<CXFA_Items>(0, XFA_Element::Items, false)) {
        CXFA_Node* pText = pItems->GetFirstChild();
        if (pText)
          wsValue = pText->JSObject()->GetContent(false);
      }
    }
    CXFA_Node* pChild = node->GetFirstChild();
    for (; pChild; pChild = pChild->GetNextSibling()) {
      if (pChild->GetElementType() != XFA_Element::Field)
        continue;
  
      CXFA_Items* pItem =
          pChild->GetChild<CXFA_Items>(0, XFA_Element::Items, false);
      if (!pItem)
        continue;
  
      CXFA_Node* pItemchild = pItem->GetFirstChild();
      if (!pItemchild)
        continue;
  
      WideString text = pItemchild->JSObject()->GetContent(false);
      WideString wsChildValue = text;
      if (wsValue != text) {
        pItemchild = pItemchild->GetNextSibling();
        if (pItemchild)
          wsChildValue = pItemchild->JSObject()->GetContent(false);
        else
          wsChildValue.clear();
      }
      pChild->SyncValue(wsChildValue, bNotify);
    }
    node->SyncValue(wsValue, bNotify);
  }
  
  CXFA_Node* CXFA_Node::GetSelectedMember() {
    CXFA_Node* pSelectedMember = nullptr;
    WideString wsState = GetRawValue();
    if (wsState.IsEmpty())
      return pSelectedMember;
  
    for (CXFA_Node* pNode = ToNode(GetFirstChild()); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetCheckState() == XFA_CHECKSTATE_On) {
        pSelectedMember = pNode;
        break;
      }
    }
    return pSelectedMember;
  }
  
  CXFA_Node* CXFA_Node::SetSelectedMember(const WideStringView& wsName,
                                          bool bNotify) {
    uint32_t nameHash = FX_HashCode_GetW(wsName, false);
    for (CXFA_Node* pNode = ToNode(GetFirstChild()); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetNameHash() == nameHash) {
        pNode->SetCheckState(XFA_CHECKSTATE_On, bNotify);
        return pNode;
      }
    }
    return nullptr;
  }
  
  void CXFA_Node::SetSelectedMemberByValue(const WideStringView& wsValue,
                                           bool bNotify,
                                           bool bScriptModify,
                                           bool bSyncData) {
    WideString wsExclGroup;
    for (CXFA_Node* pNode = GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() != XFA_Element::Field)
        continue;
  
      CXFA_Items* pItem =
          pNode->GetChild<CXFA_Items>(0, XFA_Element::Items, false);
      if (!pItem)
        continue;
  
      CXFA_Node* pItemchild = pItem->GetFirstChild();
      if (!pItemchild)
        continue;
  
      WideString wsChildValue = pItemchild->JSObject()->GetContent(false);
      if (wsValue != wsChildValue) {
        pItemchild = pItemchild->GetNextSibling();
        if (pItemchild)
          wsChildValue = pItemchild->JSObject()->GetContent(false);
        else
          wsChildValue.clear();
      } else {
        wsExclGroup = wsValue;
      }
      pNode->JSObject()->SetContent(wsChildValue, wsChildValue, bNotify,
                                    bScriptModify, false);
    }
    JSObject()->SetContent(wsExclGroup, wsExclGroup, bNotify, bScriptModify,
                           bSyncData);
  }
  
  CXFA_Node* CXFA_Node::GetExclGroupFirstMember() {
    CXFA_Node* pNode = GetFirstChild();
    while (pNode) {
      if (pNode->GetElementType() == XFA_Element::Field)
        return pNode;
  
      pNode = pNode->GetNextSibling();
    }
    return nullptr;
  }
  
  CXFA_Node* CXFA_Node::GetExclGroupNextMember(CXFA_Node* pNode) {
    if (!pNode)
      return nullptr;
  
    CXFA_Node* pNodeField = pNode->GetNextSibling();
    while (pNodeField) {
      if (pNodeField->GetElementType() == XFA_Element::Field)
        return pNodeField;
  
      pNodeField = pNodeField->GetNextSibling();
    }
    return nullptr;
  }
  
  bool CXFA_Node::IsChoiceListCommitOnSelect() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild) {
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::CommitOn) ==
             XFA_AttributeEnum::Select;
    }
    return true;
  }
  
  bool CXFA_Node::IsChoiceListAllowTextEntry() {
    CXFA_Node* pUIChild = GetUIChild();
    return pUIChild && pUIChild->JSObject()->GetBoolean(XFA_Attribute::TextEntry);
  }
  
  bool CXFA_Node::IsChoiceListMultiSelect() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild) {
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::Open) ==
             XFA_AttributeEnum::MultiSelect;
    }
    return false;
  }
  
  bool CXFA_Node::IsListBox() {
    CXFA_Node* pUIChild = GetUIChild();
    if (!pUIChild)
      return false;
  
    XFA_AttributeEnum attr = pUIChild->JSObject()->GetEnum(XFA_Attribute::Open);
    return attr == XFA_AttributeEnum::Always ||
           attr == XFA_AttributeEnum::MultiSelect;
  }
  
  int32_t CXFA_Node::CountChoiceListItems(bool bSaveValue) {
    std::vector<CXFA_Node*> pItems;
    int32_t iCount = 0;
    for (CXFA_Node* pNode = GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() != XFA_Element::Items)
        continue;
      iCount++;
      pItems.push_back(pNode);
      if (iCount == 2)
        break;
    }
    if (iCount == 0)
      return 0;
  
    CXFA_Node* pItem = pItems[0];
    if (iCount > 1) {
      bool bItemOneHasSave =
          pItems[0]->JSObject()->GetBoolean(XFA_Attribute::Save);
      bool bItemTwoHasSave =
          pItems[1]->JSObject()->GetBoolean(XFA_Attribute::Save);
      if (bItemOneHasSave != bItemTwoHasSave && bSaveValue == bItemTwoHasSave)
        pItem = pItems[1];
    }
    return pItem->CountChildren(XFA_Element::Unknown, false);
  }
  
  Optional<WideString> CXFA_Node::GetChoiceListItem(int32_t nIndex,
                                                    bool bSaveValue) {
    std::vector<CXFA_Node*> pItemsArray;
    int32_t iCount = 0;
    for (CXFA_Node* pNode = GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() != XFA_Element::Items)
        continue;
  
      ++iCount;
      pItemsArray.push_back(pNode);
      if (iCount == 2)
        break;
    }
    if (iCount == 0)
      return {};
  
    CXFA_Node* pItems = pItemsArray[0];
    if (iCount > 1) {
      bool bItemOneHasSave =
          pItemsArray[0]->JSObject()->GetBoolean(XFA_Attribute::Save);
      bool bItemTwoHasSave =
          pItemsArray[1]->JSObject()->GetBoolean(XFA_Attribute::Save);
      if (bItemOneHasSave != bItemTwoHasSave && bSaveValue == bItemTwoHasSave)
        pItems = pItemsArray[1];
    }
    if (!pItems)
      return {};
  
    CXFA_Node* pItem =
        pItems->GetChild<CXFA_Node>(nIndex, XFA_Element::Unknown, false);
    if (pItem)
      return {pItem->JSObject()->GetContent(false)};
    return {};
  }
  
  std::vector<WideString> CXFA_Node::GetChoiceListItems(bool bSaveValue) {
    std::vector<CXFA_Node*> items;
    for (CXFA_Node* pNode = GetFirstChild(); pNode && items.size() < 2;
         pNode = pNode->GetNextSibling()) {
      if (pNode->GetElementType() == XFA_Element::Items)
        items.push_back(pNode);
    }
    if (items.empty())
      return std::vector<WideString>();
  
    CXFA_Node* pItem = items.front();
    if (items.size() > 1) {
      bool bItemOneHasSave =
          items[0]->JSObject()->GetBoolean(XFA_Attribute::Save);
      bool bItemTwoHasSave =
          items[1]->JSObject()->GetBoolean(XFA_Attribute::Save);
      if (bItemOneHasSave != bItemTwoHasSave && bSaveValue == bItemTwoHasSave)
        pItem = items[1];
    }
  
    std::vector<WideString> wsTextArray;
    for (CXFA_Node* pNode = pItem->GetFirstChild(); pNode;
         pNode = pNode->GetNextSibling()) {
      wsTextArray.emplace_back(pNode->JSObject()->GetContent(false));
    }
    return wsTextArray;
  }
  
  int32_t CXFA_Node::CountSelectedItems() {
    std::vector<WideString> wsValueArray = GetSelectedItemsValue();
    if (IsListBox() || !IsChoiceListAllowTextEntry())
      return pdfium::CollectionSize<int32_t>(wsValueArray);
  
    int32_t iSelected = 0;
    std::vector<WideString> wsSaveTextArray = GetChoiceListItems(true);
    for (const auto& value : wsValueArray) {
      if (pdfium::ContainsValue(wsSaveTextArray, value))
        iSelected++;
    }
    return iSelected;
  }
  
  int32_t CXFA_Node::GetSelectedItem(int32_t nIndex) {
    std::vector<WideString> wsValueArray = GetSelectedItemsValue();
    if (!pdfium::IndexInBounds(wsValueArray, nIndex))
      return -1;
  
    std::vector<WideString> wsSaveTextArray = GetChoiceListItems(true);
    auto it = std::find(wsSaveTextArray.begin(), wsSaveTextArray.end(),
                        wsValueArray[nIndex]);
    return it != wsSaveTextArray.end() ? it - wsSaveTextArray.begin() : -1;
  }
  
  std::vector<int32_t> CXFA_Node::GetSelectedItems() {
    std::vector<int32_t> iSelArray;
    std::vector<WideString> wsValueArray = GetSelectedItemsValue();
    std::vector<WideString> wsSaveTextArray = GetChoiceListItems(true);
    for (const auto& value : wsValueArray) {
      auto it = std::find(wsSaveTextArray.begin(), wsSaveTextArray.end(), value);
      if (it != wsSaveTextArray.end())
        iSelArray.push_back(it - wsSaveTextArray.begin());
    }
    return iSelArray;
  }
  
  std::vector<WideString> CXFA_Node::GetSelectedItemsValue() {
    std::vector<WideString> wsSelTextArray;
    WideString wsValue = GetRawValue();
    if (IsChoiceListMultiSelect()) {
      if (!wsValue.IsEmpty()) {
        size_t iStart = 0;
        size_t iLength = wsValue.GetLength();
        auto iEnd = wsValue.Find(L'\n', iStart);
        iEnd = (!iEnd.has_value()) ? iLength : iEnd;
        while (iEnd >= iStart) {
          wsSelTextArray.push_back(wsValue.Mid(iStart, iEnd.value() - iStart));
          iStart = iEnd.value() + 1;
          if (iStart >= iLength)
            break;
          iEnd = wsValue.Find(L'\n', iStart);
          if (!iEnd.has_value())
            wsSelTextArray.push_back(wsValue.Mid(iStart, iLength - iStart));
        }
      }
    } else {
      wsSelTextArray.push_back(wsValue);
    }
    return wsSelTextArray;
  }
  
  bool CXFA_Node::GetItemState(int32_t nIndex) {
    std::vector<WideString> wsSaveTextArray = GetChoiceListItems(true);
    return pdfium::IndexInBounds(wsSaveTextArray, nIndex) &&
           pdfium::ContainsValue(GetSelectedItemsValue(),
                                 wsSaveTextArray[nIndex]);
  }
  
  void CXFA_Node::SetItemState(int32_t nIndex,
                               bool bSelected,
                               bool bNotify,
                               bool bScriptModify,
                               bool bSyncData) {
    std::vector<WideString> wsSaveTextArray = GetChoiceListItems(true);
    if (!pdfium::IndexInBounds(wsSaveTextArray, nIndex))
      return;
  
    int32_t iSel = -1;
    std::vector<WideString> wsValueArray = GetSelectedItemsValue();
    auto it = std::find(wsValueArray.begin(), wsValueArray.end(),
                        wsSaveTextArray[nIndex]);
    if (it != wsValueArray.end())
      iSel = it - wsValueArray.begin();
  
    if (IsChoiceListMultiSelect()) {
      if (bSelected) {
        if (iSel < 0) {
          WideString wsValue = GetRawValue();
          if (!wsValue.IsEmpty()) {
            wsValue += L"\n";
          }
          wsValue += wsSaveTextArray[nIndex];
          JSObject()->SetContent(wsValue, wsValue, bNotify, bScriptModify,
                                 bSyncData);
        }
      } else if (iSel >= 0) {
        std::vector<int32_t> iSelArray = GetSelectedItems();
        auto it = std::find(iSelArray.begin(), iSelArray.end(), nIndex);
        if (it != iSelArray.end())
          iSelArray.erase(it);
        SetSelectedItems(iSelArray, bNotify, bScriptModify, bSyncData);
      }
    } else {
      if (bSelected) {
        if (iSel < 0) {
          WideString wsSaveText = wsSaveTextArray[nIndex];
          JSObject()->SetContent(wsSaveText, GetFormatDataValue(wsSaveText),
                                 bNotify, bScriptModify, bSyncData);
        }
      } else if (iSel >= 0) {
        JSObject()->SetContent(WideString(), WideString(), bNotify, bScriptModify,
                               bSyncData);
      }
    }
  }
  
  void CXFA_Node::SetSelectedItems(const std::vector<int32_t>& iSelArray,
                                   bool bNotify,
                                   bool bScriptModify,
                                   bool bSyncData) {
    WideString wsValue;
    int32_t iSize = pdfium::CollectionSize<int32_t>(iSelArray);
    if (iSize >= 1) {
      std::vector<WideString> wsSaveTextArray = GetChoiceListItems(true);
      WideString wsItemValue;
      for (int32_t i = 0; i < iSize; i++) {
        wsItemValue = (iSize == 1) ? wsSaveTextArray[iSelArray[i]]
                                   : wsSaveTextArray[iSelArray[i]] + L"\n";
        wsValue += wsItemValue;
      }
    }
    WideString wsFormat(wsValue);
    if (!IsChoiceListMultiSelect())
      wsFormat = GetFormatDataValue(wsValue);
  
    JSObject()->SetContent(wsValue, wsFormat, bNotify, bScriptModify, bSyncData);
  }
  
  void CXFA_Node::ClearAllSelections() {
    CXFA_Node* pBind = GetBindData();
    if (!pBind || !IsChoiceListMultiSelect()) {
      SyncValue(WideString(), false);
      return;
    }
  
    while (CXFA_Node* pChildNode = pBind->GetFirstChild())
      pBind->RemoveChild(pChildNode, true);
  }
  
  void CXFA_Node::InsertItem(const WideString& wsLabel,
                             const WideString& wsValue,
                             bool bNotify) {
    int32_t nIndex = -1;
    WideString wsNewValue(wsValue);
    if (wsNewValue.IsEmpty())
      wsNewValue = wsLabel;
  
    std::vector<CXFA_Node*> listitems;
    for (CXFA_Node* pItem = GetFirstChild(); pItem;
         pItem = pItem->GetNextSibling()) {
      if (pItem->GetElementType() == XFA_Element::Items)
        listitems.push_back(pItem);
    }
    if (listitems.empty()) {
      CXFA_Node* pItems = CreateSamePacketNode(XFA_Element::Items);
      InsertChild(-1, pItems);
      InsertListTextItem(pItems, wsLabel, nIndex);
      CXFA_Node* pSaveItems = CreateSamePacketNode(XFA_Element::Items);
      InsertChild(-1, pSaveItems);
      pSaveItems->JSObject()->SetBoolean(XFA_Attribute::Save, true, false);
      InsertListTextItem(pSaveItems, wsNewValue, nIndex);
    } else if (listitems.size() > 1) {
      for (int32_t i = 0; i < 2; i++) {
        CXFA_Node* pNode = listitems[i];
        bool bHasSave = pNode->JSObject()->GetBoolean(XFA_Attribute::Save);
        if (bHasSave)
          InsertListTextItem(pNode, wsNewValue, nIndex);
        else
          InsertListTextItem(pNode, wsLabel, nIndex);
      }
    } else {
      CXFA_Node* pNode = listitems[0];
      pNode->JSObject()->SetBoolean(XFA_Attribute::Save, false, false);
      pNode->JSObject()->SetEnum(XFA_Attribute::Presence,
                                 XFA_AttributeEnum::Visible, false);
      CXFA_Node* pSaveItems = CreateSamePacketNode(XFA_Element::Items);
      InsertChild(-1, pSaveItems);
      pSaveItems->JSObject()->SetBoolean(XFA_Attribute::Save, true, false);
      pSaveItems->JSObject()->SetEnum(XFA_Attribute::Presence,
                                      XFA_AttributeEnum::Hidden, false);
      CXFA_Node* pListNode = pNode->GetFirstChild();
      int32_t i = 0;
      while (pListNode) {
        InsertListTextItem(pSaveItems, pListNode->JSObject()->GetContent(false),
                           i);
        ++i;
  
        pListNode = pListNode->GetNextSibling();
      }
      InsertListTextItem(pNode, wsLabel, nIndex);
      InsertListTextItem(pSaveItems, wsNewValue, nIndex);
    }
    if (!bNotify)
      return;
  
    GetDocument()->GetNotify()->OnWidgetListItemAdded(this, wsLabel.c_str(),
                                                      wsValue.c_str(), nIndex);
  }
  
  void CXFA_Node::GetItemLabel(const WideStringView& wsValue,
                               WideString& wsLabel) {
    int32_t iCount = 0;
    std::vector<CXFA_Node*> listitems;
    CXFA_Node* pItems = GetFirstChild();
    for (; pItems; pItems = pItems->GetNextSibling()) {
      if (pItems->GetElementType() != XFA_Element::Items)
        continue;
      iCount++;
      listitems.push_back(pItems);
    }
  
    if (iCount <= 1) {
      wsLabel = wsValue;
      return;
    }
  
    CXFA_Node* pLabelItems = listitems[0];
    bool bSave = pLabelItems->JSObject()->GetBoolean(XFA_Attribute::Save);
    CXFA_Node* pSaveItems = nullptr;
    if (bSave) {
      pSaveItems = pLabelItems;
      pLabelItems = listitems[1];
    } else {
      pSaveItems = listitems[1];
    }
    iCount = 0;
  
    int32_t iSearch = -1;
    for (CXFA_Node* pChildItem = pSaveItems->GetFirstChild(); pChildItem;
         pChildItem = pChildItem->GetNextSibling()) {
      if (pChildItem->JSObject()->GetContent(false) == wsValue) {
        iSearch = iCount;
        break;
      }
      iCount++;
    }
    if (iSearch < 0)
      return;
  
    CXFA_Node* pText =
        pLabelItems->GetChild<CXFA_Node>(iSearch, XFA_Element::Unknown, false);
    if (pText)
      wsLabel = pText->JSObject()->GetContent(false);
  }
  
  WideString CXFA_Node::GetItemValue(const WideStringView& wsLabel) {
    int32_t iCount = 0;
    std::vector<CXFA_Node*> listitems;
    for (CXFA_Node* pItems = GetFirstChild(); pItems;
         pItems = pItems->GetNextSibling()) {
      if (pItems->GetElementType() != XFA_Element::Items)
        continue;
      iCount++;
      listitems.push_back(pItems);
    }
    if (iCount <= 1)
      return WideString(wsLabel);
  
    CXFA_Node* pLabelItems = listitems[0];
    bool bSave = pLabelItems->JSObject()->GetBoolean(XFA_Attribute::Save);
    CXFA_Node* pSaveItems = nullptr;
    if (bSave) {
      pSaveItems = pLabelItems;
      pLabelItems = listitems[1];
    } else {
      pSaveItems = listitems[1];
    }
    iCount = 0;
  
    int32_t iSearch = -1;
    WideString wsContent;
    CXFA_Node* pChildItem = pLabelItems->GetFirstChild();
    for (; pChildItem; pChildItem = pChildItem->GetNextSibling()) {
      if (pChildItem->JSObject()->GetContent(false) == wsLabel) {
        iSearch = iCount;
        break;
      }
      iCount++;
    }
    if (iSearch < 0)
      return L"";
  
    CXFA_Node* pText =
        pSaveItems->GetChild<CXFA_Node>(iSearch, XFA_Element::Unknown, false);
    return pText ? pText->JSObject()->GetContent(false) : L"";
  }
  
  bool CXFA_Node::DeleteItem(int32_t nIndex, bool bNotify, bool bScriptModify) {
    bool bSetValue = false;
    CXFA_Node* pItems = GetFirstChild();
    for (; pItems; pItems = pItems->GetNextSibling()) {
      if (pItems->GetElementType() != XFA_Element::Items)
        continue;
  
      if (nIndex < 0) {
        while (CXFA_Node* pNode = pItems->GetFirstChild()) {
          pItems->RemoveChild(pNode, true);
        }
      } else {
        if (!bSetValue && pItems->JSObject()->GetBoolean(XFA_Attribute::Save)) {
          SetItemState(nIndex, false, true, bScriptModify, true);
          bSetValue = true;
        }
        int32_t i = 0;
        CXFA_Node* pNode = pItems->GetFirstChild();
        while (pNode) {
          if (i == nIndex) {
            pItems->RemoveChild(pNode, true);
            break;
          }
          i++;
          pNode = pNode->GetNextSibling();
        }
      }
    }
    if (bNotify)
      GetDocument()->GetNotify()->OnWidgetListItemRemoved(this, nIndex);
    return true;
  }
  
  bool CXFA_Node::IsHorizontalScrollPolicyOff() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild) {
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::HScrollPolicy) ==
             XFA_AttributeEnum::Off;
    }
    return false;
  }
  
  bool CXFA_Node::IsVerticalScrollPolicyOff() {
    CXFA_Node* pUIChild = GetUIChild();
    if (pUIChild) {
      return pUIChild->JSObject()->GetEnum(XFA_Attribute::VScrollPolicy) ==
             XFA_AttributeEnum::Off;
    }
    return false;
  }
  
  Optional<int32_t> CXFA_Node::GetNumberOfCells() {
    CXFA_Node* pUIChild = GetUIChild();
    if (!pUIChild)
      return {};
    if (CXFA_Comb* pNode =
            pUIChild->GetChild<CXFA_Comb>(0, XFA_Element::Comb, false))
      return {pNode->JSObject()->GetInteger(XFA_Attribute::NumberOfCells)};
    return {};
  }
  
  WideString CXFA_Node::GetPasswordChar() {
    CXFA_Node* pUIChild = GetUIChild();
    return pUIChild ? pUIChild->JSObject()->GetCData(XFA_Attribute::PasswordChar)
                    : L"*";
  }
  
  bool CXFA_Node::IsMultiLine() {
    CXFA_Node* pUIChild = GetUIChild();
    return pUIChild && pUIChild->JSObject()->GetBoolean(XFA_Attribute::MultiLine);
  }
  
  std::pair<XFA_Element, int32_t> CXFA_Node::GetMaxChars() {
    if (CXFA_Value* pNode = GetChild<CXFA_Value>(0, XFA_Element::Value, false)) {
      if (CXFA_Node* pChild = pNode->GetFirstChild()) {
        switch (pChild->GetElementType()) {
          case XFA_Element::Text:
            return {XFA_Element::Text,
                    pChild->JSObject()->GetInteger(XFA_Attribute::MaxChars)};
          case XFA_Element::ExData: {
            int32_t iMax =
                pChild->JSObject()->GetInteger(XFA_Attribute::MaxLength);
            return {XFA_Element::ExData, iMax < 0 ? 0 : iMax};
          }
          default:
            break;
        }
      }
    }
    return {XFA_Element::Unknown, 0};
  }
  
  int32_t CXFA_Node::GetFracDigits() {
    CXFA_Value* pNode = GetChild<CXFA_Value>(0, XFA_Element::Value, false);
    if (!pNode)
      return -1;
  
    CXFA_Decimal* pChild =
        pNode->GetChild<CXFA_Decimal>(0, XFA_Element::Decimal, false);
    if (!pChild)
      return -1;
  
    return pChild->JSObject()
        ->TryInteger(XFA_Attribute::FracDigits, true)
        .value_or(-1);
  }
  
  int32_t CXFA_Node::GetLeadDigits() {
    CXFA_Value* pNode = GetChild<CXFA_Value>(0, XFA_Element::Value, false);
    if (!pNode)
      return -1;
  
    CXFA_Decimal* pChild =
        pNode->GetChild<CXFA_Decimal>(0, XFA_Element::Decimal, false);
    if (!pChild)
      return -1;
  
    return pChild->JSObject()
        ->TryInteger(XFA_Attribute::LeadDigits, true)
        .value_or(-1);
  }
  
  bool CXFA_Node::SetValue(XFA_VALUEPICTURE eValueType,
                           const WideString& wsValue) {
    if (wsValue.IsEmpty()) {
      SyncValue(wsValue, true);
      return true;
    }
  
    SetPreNull(IsNull());
    SetIsNull(false);
  
    WideString wsNewText(wsValue);
    WideString wsPicture = GetPictureContent(eValueType);
    bool bValidate = true;
    bool bSyncData = false;
    CXFA_Node* pNode = GetUIChild();
    if (!pNode)
      return true;
  
    XFA_Element eType = pNode->GetElementType();
    if (!wsPicture.IsEmpty()) {
      CXFA_LocaleMgr* pLocalMgr = GetDocument()->GetLocalMgr();
      IFX_Locale* pLocale = GetLocale();
      CXFA_LocaleValue widgetValue = XFA_GetLocaleValue(this);
      bValidate =
          widgetValue.ValidateValue(wsValue, wsPicture, pLocale, &wsPicture);
      if (bValidate) {
        widgetValue = CXFA_LocaleValue(widgetValue.GetType(), wsNewText,
                                       wsPicture, pLocale, pLocalMgr);
        wsNewText = widgetValue.GetValue();
        if (eType == XFA_Element::NumericEdit)
          wsNewText = NumericLimit(wsNewText, GetLeadDigits(), GetFracDigits());
  
        bSyncData = true;
      }
    } else {
      if (eType == XFA_Element::NumericEdit) {
        if (wsNewText != L"0")
          wsNewText = NumericLimit(wsNewText, GetLeadDigits(), GetFracDigits());
  
        bSyncData = true;
      }
    }
    if (eType != XFA_Element::NumericEdit || bSyncData)
      SyncValue(wsNewText, true);
  
    return bValidate;
  }
  
  WideString CXFA_Node::GetPictureContent(XFA_VALUEPICTURE ePicture) {
    if (ePicture == XFA_VALUEPICTURE_Raw)
      return L"";
  
    CXFA_LocaleValue widgetValue = XFA_GetLocaleValue(this);
    switch (ePicture) {
      case XFA_VALUEPICTURE_Display: {
        if (CXFA_Format* pFormat =
                GetChild<CXFA_Format>(0, XFA_Element::Format, false)) {
          if (CXFA_Picture* pPicture = pFormat->GetChild<CXFA_Picture>(
                  0, XFA_Element::Picture, false)) {
            Optional<WideString> picture =
                pPicture->JSObject()->TryContent(false, true);
            if (picture)
              return *picture;
          }
        }
  
        IFX_Locale* pLocale = GetLocale();
        if (!pLocale)
          return L"";
  
        uint32_t dwType = widgetValue.GetType();
        switch (dwType) {
          case XFA_VT_DATE:
            return pLocale->GetDatePattern(FX_LOCALEDATETIMESUBCATEGORY_Medium);
          case XFA_VT_TIME:
            return pLocale->GetTimePattern(FX_LOCALEDATETIMESUBCATEGORY_Medium);
          case XFA_VT_DATETIME:
            return pLocale->GetDatePattern(FX_LOCALEDATETIMESUBCATEGORY_Medium) +
                   L"T" +
                   pLocale->GetTimePattern(FX_LOCALEDATETIMESUBCATEGORY_Medium);
          case XFA_VT_DECIMAL:
          case XFA_VT_FLOAT:
          default:
            return L"";
        }
      }
      case XFA_VALUEPICTURE_Edit: {
        CXFA_Ui* pUI = GetChild<CXFA_Ui>(0, XFA_Element::Ui, false);
        if (pUI) {
          if (CXFA_Picture* pPicture =
                  pUI->GetChild<CXFA_Picture>(0, XFA_Element::Picture, false)) {
            Optional<WideString> picture =
                pPicture->JSObject()->TryContent(false, true);
            if (picture)
              return *picture;
          }
        }
  
        IFX_Locale* pLocale = GetLocale();
        if (!pLocale)
          return L"";
  
        uint32_t dwType = widgetValue.GetType();
        switch (dwType) {
          case XFA_VT_DATE:
            return pLocale->GetDatePattern(FX_LOCALEDATETIMESUBCATEGORY_Short);
          case XFA_VT_TIME:
            return pLocale->GetTimePattern(FX_LOCALEDATETIMESUBCATEGORY_Short);
          case XFA_VT_DATETIME:
            return pLocale->GetDatePattern(FX_LOCALEDATETIMESUBCATEGORY_Short) +
                   L"T" +
                   pLocale->GetTimePattern(FX_LOCALEDATETIMESUBCATEGORY_Short);
          default:
            return L"";
        }
      }
      case XFA_VALUEPICTURE_DataBind: {
        CXFA_Bind* bind = GetBindIfExists();
        if (bind)
          return bind->GetPicture();
        break;
      }
      default:
        break;
    }
    return L"";
  }
  
  WideString CXFA_Node::GetValue(XFA_VALUEPICTURE eValueType) {
    WideString wsValue = JSObject()->GetContent(false);
  
    if (eValueType == XFA_VALUEPICTURE_Display)
      GetItemLabel(wsValue.AsStringView(), wsValue);
  
    WideString wsPicture = GetPictureContent(eValueType);
    CXFA_Node* pNode = GetUIChild();
    if (!pNode)
      return wsValue;
  
    switch (GetUIChild()->GetElementType()) {
      case XFA_Element::ChoiceList: {
        if (eValueType == XFA_VALUEPICTURE_Display) {
          int32_t iSelItemIndex = GetSelectedItem(0);
          if (iSelItemIndex >= 0) {
            wsValue = GetChoiceListItem(iSelItemIndex, false).value_or(L"");
            wsPicture.clear();
          }
        }
      } break;
      case XFA_Element::NumericEdit:
        if (eValueType != XFA_VALUEPICTURE_Raw && wsPicture.IsEmpty()) {
          IFX_Locale* pLocale = GetLocale();
          if (eValueType == XFA_VALUEPICTURE_Display && pLocale)
            wsValue = FormatNumStr(NormalizeNumStr(wsValue), pLocale);
        }
        break;
      default:
        break;
    }
    if (wsPicture.IsEmpty())
      return wsValue;
  
    if (IFX_Locale* pLocale = GetLocale()) {
      CXFA_LocaleValue widgetValue = XFA_GetLocaleValue(this);
      CXFA_LocaleMgr* pLocalMgr = GetDocument()->GetLocalMgr();
      switch (widgetValue.GetType()) {
        case XFA_VT_DATE: {
          WideString wsDate, wsTime;
          if (SplitDateTime(wsValue, wsDate, wsTime)) {
            CXFA_LocaleValue date(XFA_VT_DATE, wsDate, pLocalMgr);
            if (date.FormatPatterns(wsValue, wsPicture, pLocale, eValueType))
              return wsValue;
          }
          break;
        }
        case XFA_VT_TIME: {
          WideString wsDate, wsTime;
          if (SplitDateTime(wsValue, wsDate, wsTime)) {
            CXFA_LocaleValue time(XFA_VT_TIME, wsTime, pLocalMgr);
            if (time.FormatPatterns(wsValue, wsPicture, pLocale, eValueType))
              return wsValue;
          }
          break;
        }
        default:
          break;
      }
      widgetValue.FormatPatterns(wsValue, wsPicture, pLocale, eValueType);
    }
    return wsValue;
  }
  
  WideString CXFA_Node::GetNormalizeDataValue(const WideString& wsValue) {
    if (wsValue.IsEmpty())
      return L"";
  
    WideString wsPicture = GetPictureContent(XFA_VALUEPICTURE_DataBind);
    if (wsPicture.IsEmpty())
      return wsValue;
  
    CXFA_LocaleMgr* pLocalMgr = GetDocument()->GetLocalMgr();
    IFX_Locale* pLocale = GetLocale();
    CXFA_LocaleValue widgetValue = XFA_GetLocaleValue(this);
    if (widgetValue.ValidateValue(wsValue, wsPicture, pLocale, &wsPicture)) {
      widgetValue = CXFA_LocaleValue(widgetValue.GetType(), wsValue, wsPicture,
                                     pLocale, pLocalMgr);
      return widgetValue.GetValue();
    }
    return wsValue;
  }
  
  WideString CXFA_Node::GetFormatDataValue(const WideString& wsValue) {
    if (wsValue.IsEmpty())
      return L"";
  
    WideString wsPicture = GetPictureContent(XFA_VALUEPICTURE_DataBind);
    if (wsPicture.IsEmpty())
      return wsValue;
  
    WideString wsFormattedValue = wsValue;
    if (IFX_Locale* pLocale = GetLocale()) {
      CXFA_Value* pNodeValue = GetChild<CXFA_Value>(0, XFA_Element::Value, false);
      if (!pNodeValue)
        return wsValue;
  
      CXFA_Node* pValueChild = pNodeValue->GetFirstChild();
      if (!pValueChild)
        return wsValue;
  
      int32_t iVTType = XFA_VT_NULL;
      switch (pValueChild->GetElementType()) {
        case XFA_Element::Decimal:
          iVTType = XFA_VT_DECIMAL;
          break;
        case XFA_Element::Float:
          iVTType = XFA_VT_FLOAT;
          break;
        case XFA_Element::Date:
          iVTType = XFA_VT_DATE;
          break;
        case XFA_Element::Time:
          iVTType = XFA_VT_TIME;
          break;
        case XFA_Element::DateTime:
          iVTType = XFA_VT_DATETIME;
          break;
        case XFA_Element::Boolean:
          iVTType = XFA_VT_BOOLEAN;
          break;
        case XFA_Element::Integer:
          iVTType = XFA_VT_INTEGER;
          break;
        case XFA_Element::Text:
          iVTType = XFA_VT_TEXT;
          break;
        default:
          iVTType = XFA_VT_NULL;
          break;
      }
      CXFA_LocaleMgr* pLocalMgr = GetDocument()->GetLocalMgr();
      CXFA_LocaleValue widgetValue(iVTType, wsValue, pLocalMgr);
      switch (widgetValue.GetType()) {
        case XFA_VT_DATE: {
          WideString wsDate, wsTime;
          if (SplitDateTime(wsValue, wsDate, wsTime)) {
            CXFA_LocaleValue date(XFA_VT_DATE, wsDate, pLocalMgr);
            if (date.FormatPatterns(wsFormattedValue, wsPicture, pLocale,
                                    XFA_VALUEPICTURE_DataBind)) {
              return wsFormattedValue;
            }
          }
          break;
        }
        case XFA_VT_TIME: {
          WideString wsDate, wsTime;
          if (SplitDateTime(wsValue, wsDate, wsTime)) {
            CXFA_LocaleValue time(XFA_VT_TIME, wsTime, pLocalMgr);
            if (time.FormatPatterns(wsFormattedValue, wsPicture, pLocale,
                                    XFA_VALUEPICTURE_DataBind)) {
              return wsFormattedValue;
            }
          }
          break;
        }
        default:
          break;
      }
      widgetValue.FormatPatterns(wsFormattedValue, wsPicture, pLocale,
                                 XFA_VALUEPICTURE_DataBind);
    }
    return wsFormattedValue;
  }
  
  WideString CXFA_Node::NormalizeNumStr(const WideString& wsValue) {
    if (wsValue.IsEmpty())
      return L"";
  
    WideString wsOutput = wsValue;
    wsOutput.TrimLeft('0');
  
    if (!wsOutput.IsEmpty() && wsOutput.Contains('.') && GetFracDigits() != -1) {
      wsOutput.TrimRight(L"0");
      wsOutput.TrimRight(L".");
    }
    if (wsOutput.IsEmpty() || wsOutput[0] == '.')
      wsOutput.InsertAtFront('0');
  
    return wsOutput;
  }
  
  WideString CXFA_Node::FormatNumStr(const WideString& wsValue,
                                     IFX_Locale* pLocale) {
    if (wsValue.IsEmpty())
      return L"";
  
    WideString wsSrcNum = wsValue;
    WideString wsGroupSymbol =
        pLocale->GetNumbericSymbol(FX_LOCALENUMSYMBOL_Grouping);
    bool bNeg = false;
    if (wsSrcNum[0] == '-') {
      bNeg = true;
      wsSrcNum.Delete(0, 1);
    }
  
    auto dot_index = wsSrcNum.Find('.');
    dot_index = !dot_index.has_value() ? wsSrcNum.GetLength() : dot_index;
  
    if (dot_index.value() < 1)
      return L"";
  
    size_t nPos = dot_index.value() % 3;
    WideString wsOutput;
    for (size_t i = 0; i < dot_index.value(); i++) {
      if (i % 3 == nPos && i != 0)
        wsOutput += wsGroupSymbol;
  
      wsOutput += wsSrcNum[i];
    }
    if (dot_index.value() < wsSrcNum.GetLength()) {
      wsOutput += pLocale->GetNumbericSymbol(FX_LOCALENUMSYMBOL_Decimal);
      wsOutput += wsSrcNum.Right(wsSrcNum.GetLength() - dot_index.value() - 1);
    }
    if (bNeg)
      return pLocale->GetNumbericSymbol(FX_LOCALENUMSYMBOL_Minus) + wsOutput;
  
    return wsOutput;
  }
  
  void CXFA_Node::InsertListTextItem(CXFA_Node* pItems,
                                     const WideString& wsText,
                                     int32_t nIndex) {
    CXFA_Node* pText = pItems->CreateSamePacketNode(XFA_Element::Text);
    pItems->InsertChild(nIndex, pText);
    pText->JSObject()->SetContent(wsText, wsText, false, false, false);
  }
  
  WideString CXFA_Node::NumericLimit(const WideString& wsValue,
                                     int32_t iLead,
                                     int32_t iTread) const {
    if ((iLead == -1) && (iTread == -1))
      return wsValue;
  
    WideString wsRet;
    int32_t iLead_ = 0, iTread_ = -1;
    int32_t iCount = wsValue.GetLength();
    if (iCount == 0)
      return wsValue;
  
    int32_t i = 0;
    if (wsValue[i] == L'-') {
      wsRet += L'-';
      i++;
    }
    for (; i < iCount; i++) {
      wchar_t wc = wsValue[i];
      if (FXSYS_isDecimalDigit(wc)) {
        if (iLead >= 0) {
          iLead_++;
          if (iLead_ > iLead)
            return L"0";
        } else if (iTread_ >= 0) {
          iTread_++;
          if (iTread_ > iTread) {
            if (iTread != -1) {
              CFX_Decimal wsDeci = CFX_Decimal(wsValue.AsStringView());
              wsDeci.SetScale(iTread);
              wsRet = wsDeci;
            }
            return wsRet;
          }
        }
      } else if (wc == L'.') {
        iTread_ = 0;
        iLead = -1;
      }
      wsRet += wc;
    }
    return wsRet;
  }
  