// Copyright 2017 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 "core/fxcrt/xml/cxml_element.h"

#include "core/fxcrt/xml/cxml_content.h"
#include "core/fxcrt/xml/cxml_parser.h"

namespace {

void SplitQualifiedName(const ByteStringView& bsFullName,
                        ByteStringView* bsSpace,
                        ByteStringView* bsName) {
  if (bsFullName.IsEmpty())
    return;

  auto iStart = bsFullName.Find(':');
  if (iStart.has_value()) {
    *bsSpace = bsFullName.Left(iStart.value());
    *bsName = bsFullName.Right(bsFullName.GetLength() - (iStart.value() + 1));
  } else {
    *bsName = bsFullName;
  }
}

}  // namespace

// static
std::unique_ptr<CXML_Element> CXML_Element::Parse(const void* pBuffer,
                                                  size_t size) {
  CXML_Parser parser;
  if (!parser.Init(static_cast<const uint8_t*>(pBuffer), size))
    return nullptr;
  return parser.ParseElement(nullptr, false);
}

CXML_Element::CXML_Element(const CXML_Element* pParent,
                           const ByteStringView& qSpace,
                           const ByteStringView& tagname)
    : m_pParent(pParent), m_QSpaceName(qSpace), m_TagName(tagname) {}

CXML_Element::~CXML_Element() {}

CXML_Element* CXML_Element::AsElement() {
  return this;
}

const CXML_Element* CXML_Element::AsElement() const {
  return this;
}

ByteString CXML_Element::GetTagName() const {
  return m_TagName;
}

ByteString CXML_Element::GetNamespaceURI(const ByteString& qName) const {
  const CXML_Element* pElement = this;
  do {
    const WideString* pwsSpace;
    if (qName.IsEmpty())
      pwsSpace = pElement->Lookup("", "xmlns");
    else
      pwsSpace = pElement->Lookup("xmlns", qName);
    if (pwsSpace)
      return pwsSpace->UTF8Encode();

    pElement = pElement->GetParent();
  } while (pElement);
  return ByteString();
}

void CXML_Element::GetAttrByIndex(size_t index,
                                  ByteString* space,
                                  ByteString* name,
                                  WideString* value) const {
  if (index >= m_AttrMap.size())
    return;

  const CXML_AttrItem& item = m_AttrMap[index];
  *space = item.m_QSpaceName;
  *name = item.m_AttrName;
  *value = item.m_Value;
}

WideString CXML_Element::GetAttrValue(const ByteStringView& name) const {
  ByteStringView bsSpace;
  ByteStringView bsName;
  SplitQualifiedName(name, &bsSpace, &bsName);

  WideString attr;
  const WideString* pValue = Lookup(ByteString(bsSpace), ByteString(bsName));
  if (pValue)
    attr = *pValue;
  return attr;
}

int CXML_Element::GetAttrInteger(const ByteStringView& name) const {
  ByteStringView bsSpace;
  ByteStringView bsName;
  SplitQualifiedName(name, &bsSpace, &bsName);

  const WideString* pwsValue = Lookup(ByteString(bsSpace), ByteString(bsName));
  return pwsValue ? pwsValue->GetInteger() : 0;
}

size_t CXML_Element::CountElements(const ByteStringView& space,
                                   const ByteStringView& tag) const {
  size_t count = 0;
  for (const auto& pChild : m_Children) {
    const CXML_Element* pKid = pChild->AsElement();
    if (MatchesElement(pKid, space, tag))
      count++;
  }
  return count;
}

CXML_Object* CXML_Element::GetChild(size_t index) const {
  return index < m_Children.size() ? m_Children[index].get() : nullptr;
}

CXML_Element* CXML_Element::GetElement(const ByteStringView& space,
                                       const ByteStringView& tag,
                                       size_t nth) const {
  for (const auto& pChild : m_Children) {
    CXML_Element* pKid = pChild->AsElement();
    if (MatchesElement(pKid, space, tag)) {
      if (nth == 0)
        return pKid;
      --nth;
    }
  }
  return nullptr;
}

void CXML_Element::SetAttribute(const ByteString& space,
                                const ByteString& name,
                                const WideString& value) {
  for (CXML_AttrItem& item : m_AttrMap) {
    if (item.Matches(space, name)) {
      item.m_Value = value;
      return;
    }
  }
  m_AttrMap.push_back({space, name, WideString(value)});
}

// static
bool CXML_Element::MatchesElement(const CXML_Element* pKid,
                                  const ByteStringView& space,
                                  const ByteStringView& tag) {
  return pKid && pKid->m_TagName == tag &&
         (space.IsEmpty() || pKid->m_QSpaceName == space);
}

const WideString* CXML_Element::Lookup(const ByteString& space,
                                       const ByteString& name) const {
  for (const CXML_AttrItem& item : m_AttrMap) {
    if (item.Matches(space, name))
      return &item.m_Value;
  }
  return nullptr;
}