// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkXMLParser.h"
#include "vtkObjectFactory.h"
#include "vtk_expat.h"

#include "vtksys/FStream.hxx"
#include <vtksys/SystemTools.hxx>

#include <cctype>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkXMLParser);

//------------------------------------------------------------------------------
vtkXMLParser::vtkXMLParser()
{
  this->Stream = nullptr;
  this->Parser = nullptr;
  this->FileName = nullptr;
  this->Encoding = nullptr;
  this->InputString = nullptr;
  this->InputStringLength = 0;
  this->ParseError = 0;
  this->IgnoreCharacterData = 0;
}

//------------------------------------------------------------------------------
vtkXMLParser::~vtkXMLParser()
{
  this->SetStream(nullptr);
  this->SetFileName(nullptr);
  this->SetEncoding(nullptr);
}

//------------------------------------------------------------------------------
void vtkXMLParser::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  if (this->Stream)
  {
    os << indent << "Stream: " << this->Stream << "\n";
  }
  else
  {
    os << indent << "Stream: (none)\n";
  }
  os << indent << "FileName: " << (this->FileName ? this->FileName : "(none)") << "\n";
  os << indent << "IgnoreCharacterData: " << (this->IgnoreCharacterData ? "On" : "Off") << endl;
  os << indent << "Encoding: " << (this->Encoding ? this->Encoding : "(none)") << "\n";
}

//------------------------------------------------------------------------------
static int vtkXMLParserFail(istream* stream)
{
  // The fail() method returns true if either the failbit or badbit is set.
#if defined(__HP_aCC)
  // The HP compiler sets the badbit too often, so we ignore it.
  return (stream->rdstate() & ios::failbit) ? 1 : 0;
#else
  return stream->fail() ? 1 : 0;
#endif
}

//------------------------------------------------------------------------------
vtkTypeInt64 vtkXMLParser::TellG()
{
  // Standard tellg returns -1 if fail() is true.
  if (!this->Stream || vtkXMLParserFail(this->Stream))
  {
    return -1;
  }
  return this->Stream->tellg();
}

//------------------------------------------------------------------------------
void vtkXMLParser::SeekG(vtkTypeInt64 position)
{
  // Standard seekg does nothing if fail() is true.
  if (!this->Stream || vtkXMLParserFail(this->Stream))
  {
    return;
  }
  this->Stream->seekg(std::streampos(position));
}

//------------------------------------------------------------------------------
int vtkXMLParser::Parse(const char* inputString)
{
  this->InputString = inputString;
  this->InputStringLength = -1;
  int result = this->Parse();
  this->InputString = nullptr;
  return result;
}

//------------------------------------------------------------------------------
int vtkXMLParser::Parse(const char* inputString, unsigned int length)
{
  this->InputString = inputString;
  this->InputStringLength = length;
  int result = this->Parse();
  this->InputString = nullptr;
  this->InputStringLength = -1;
  return result;
}

//------------------------------------------------------------------------------
int vtkXMLParser::Parse()
{
  // Select source of XML
  vtksys::ifstream ifs;
  if (!this->InputString && !this->Stream && this->FileName)
  {
    // If it is file, open it and set the appropriate stream
    vtksys::SystemTools::Stat_t fs;
    if (vtksys::SystemTools::Stat(this->FileName, &fs) != 0)
    {
      vtkErrorMacro("Cannot open XML file: " << this->FileName);
      return 0;
    }
#ifdef _WIN32
    ifs.open(this->FileName, ios::binary | ios::in);
#else
    ifs.open(this->FileName, ios::in);
#endif
    if (!ifs)
    {
      vtkErrorMacro("Cannot open XML file: " << this->FileName);
      return 0;
    }
    this->Stream = &ifs;
  }

  // Create the expat XML parser.
  this->CreateParser();

  XML_SetElementHandler(
    static_cast<XML_Parser>(this->Parser), &vtkXMLParserStartElement, &vtkXMLParserEndElement);
  if (!this->IgnoreCharacterData)
  {
    XML_SetCharacterDataHandler(
      static_cast<XML_Parser>(this->Parser), &vtkXMLParserCharacterDataHandler);
  }
  else
  {
    XML_SetCharacterDataHandler(static_cast<XML_Parser>(this->Parser), nullptr);
  }
  XML_SetUserData(static_cast<XML_Parser>(this->Parser), this);

  // Parse the input.
  int result = this->ParseXML();

  if (result)
  {
    // Tell the expat XML parser about the end-of-input.
    if (!XML_Parse(static_cast<XML_Parser>(this->Parser), "", 0, 1))
    {
      this->ReportXmlParseError();
      result = 0;
    }
  }

  // Clean up the parser.
  XML_ParserFree(static_cast<XML_Parser>(this->Parser));
  this->Parser = nullptr;

  // If the source was a file, reset the stream
  if (this->Stream == &ifs)
  {
    this->Stream = nullptr;
  }

  return result;
}

//------------------------------------------------------------------------------
int vtkXMLParser::CreateParser()
{
  if (this->Parser)
  {
    vtkErrorMacro("Parser already created");
    return 0;
  }
  // Create the expat XML parser.
  this->Parser = XML_ParserCreate(this->Encoding);
  return this->Parser ? 1 : 0;
}

//------------------------------------------------------------------------------
int vtkXMLParser::InitializeParser()
{
  // Create the expat XML parser.
  if (!this->CreateParser())
  {
    vtkErrorMacro("Parser already initialized");
    this->ParseError = 1;
    return 0;
  }

  XML_SetElementHandler(
    static_cast<XML_Parser>(this->Parser), &vtkXMLParserStartElement, &vtkXMLParserEndElement);
  if (!this->IgnoreCharacterData)
  {
    XML_SetCharacterDataHandler(
      static_cast<XML_Parser>(this->Parser), &vtkXMLParserCharacterDataHandler);
  }
  else
  {
    XML_SetCharacterDataHandler(static_cast<XML_Parser>(this->Parser), nullptr);
  }
  XML_SetUserData(static_cast<XML_Parser>(this->Parser), this);
  this->ParseError = 0;
  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLParser::ParseChunk(const char* inputString, unsigned int length)
{
  if (!this->Parser)
  {
    vtkErrorMacro("Parser not initialized");
    this->ParseError = 1;
    return 0;
  }
  int res;
  res = this->ParseBuffer(inputString, length);
  if (res == 0)
  {
    this->ParseError = 1;
  }
  return res;
}

//------------------------------------------------------------------------------
int vtkXMLParser::CleanupParser()
{
  if (!this->Parser)
  {
    vtkErrorMacro("Parser not initialized");
    this->ParseError = 1;
    return 0;
  }
  int result = !this->ParseError;
  if (result)
  {
    // Tell the expat XML parser about the end-of-input.
    if (!XML_Parse(static_cast<XML_Parser>(this->Parser), "", 0, 1))
    {
      this->ReportXmlParseError();
      result = 0;
    }
  }

  // Clean up the parser.
  XML_ParserFree(static_cast<XML_Parser>(this->Parser));
  this->Parser = nullptr;

  return result;
}

//------------------------------------------------------------------------------
int vtkXMLParser::ParseXML()
{
  // Parsing of message
  if (this->InputString)
  {
    if (this->InputStringLength >= 0)
    {
      return this->ParseBuffer(this->InputString, this->InputStringLength);
    }
    else
    {
      return this->ParseBuffer(this->InputString);
    }
  }

  // Make sure we have input.
  if (!this->Stream)
  {
    vtkErrorMacro("Parse() called with no Stream set.");
    return 0;
  }

  // Default stream parser just reads a block at a time.
  istream& in = *(this->Stream);
  const int bufferSize = 4096;
  char buffer[bufferSize];

  // Read in the stream and send its contents to the XML parser.  This
  // read loop is very sensitive on certain platforms with slightly
  // broken stream libraries (like HPUX).  Normally, it is incorrect
  // to not check the error condition on the fin.read() before using
  // the data, but the fin.gcount() will be zero if an error occurred.
  // Therefore, the loop should be safe everywhere.
  while (!this->ParseError && !this->ParsingComplete() && in)
  {
    in.read(buffer, bufferSize);
    if (in.gcount())
    {
      if (!this->ParseBuffer(buffer, in.gcount()))
      {
        return 0;
      }
    }
  }

  // Clear the fail and eof bits on the input stream so we can later
  // seek back to read data.
  this->Stream->clear(this->Stream->rdstate() & ~ios::eofbit);
  this->Stream->clear(this->Stream->rdstate() & ~ios::failbit);

  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLParser::ParsingComplete()
{
  // Default behavior is to parse to end of stream.
  return 0;
}

//------------------------------------------------------------------------------
void vtkXMLParser::StartElement(const char* name, const char** vtkNotUsed(atts))
{
  this->ReportUnknownElement(name);
}

//------------------------------------------------------------------------------
void vtkXMLParser::EndElement(const char* vtkNotUsed(name)) {}

//------------------------------------------------------------------------------
void vtkXMLParser::CharacterDataHandler(const char* vtkNotUsed(inData), int vtkNotUsed(inLength)) {}

//------------------------------------------------------------------------------
void vtkXMLParser::ReportStrayAttribute(const char* element, const char* attr, const char* value)
{
  vtkWarningMacro("Stray attribute in XML stream: Element " << element << " has " << attr << "=\""
                                                            << value << "\"");
}

//------------------------------------------------------------------------------
void vtkXMLParser::ReportMissingAttribute(const char* element, const char* attr)
{
  vtkErrorMacro("Missing attribute in XML stream: Element " << element << " is missing " << attr);
}

//------------------------------------------------------------------------------
void vtkXMLParser::ReportBadAttribute(const char* element, const char* attr, const char* value)
{
  vtkErrorMacro("Bad attribute value in XML stream: Element " << element << " has " << attr << "=\""
                                                              << value << "\"");
}

//------------------------------------------------------------------------------
void vtkXMLParser::ReportUnknownElement(const char* element)
{
  vtkErrorMacro("Unknown element in XML stream: " << element);
}

//------------------------------------------------------------------------------
void vtkXMLParser::ReportXmlParseError()
{
  vtkErrorMacro("Error parsing XML in stream at line "
    << XML_GetCurrentLineNumber(static_cast<XML_Parser>(this->Parser)) << ", column "
    << XML_GetCurrentColumnNumber(static_cast<XML_Parser>(this->Parser)) << ", byte index "
    << XML_GetCurrentByteIndex(static_cast<XML_Parser>(this->Parser)) << ": "
    << XML_ErrorString(XML_GetErrorCode(static_cast<XML_Parser>(this->Parser))));
}

//------------------------------------------------------------------------------
vtkTypeInt64 vtkXMLParser::GetXMLByteIndex()
{
  return XML_GetCurrentByteIndex(static_cast<XML_Parser>(this->Parser));
}

//------------------------------------------------------------------------------
int vtkXMLParser::ParseBuffer(const char* buffer, unsigned int count)
{
  // Pass the buffer to the expat XML parser.
  if (!XML_Parse(static_cast<XML_Parser>(this->Parser), buffer, count, 0))
  {
    this->ReportXmlParseError();
    return 0;
  }
  return 1;
}

//------------------------------------------------------------------------------
int vtkXMLParser::ParseBuffer(const char* buffer)
{
  return this->ParseBuffer(buffer, static_cast<int>(strlen(buffer)));
}

//------------------------------------------------------------------------------
int vtkXMLParser::IsSpace(char c)
{
  return isspace(c);
}

//------------------------------------------------------------------------------
bool vtkXMLParser::hasLargeOffsets()
{
  // see if expat is configured correctly to use `long long` for file offsets
  // on Windows, this is necessary for binary xml files > 2Gb.
  const XML_Feature* xmlFeature = XML_GetFeatureList();
  for (; xmlFeature && xmlFeature->feature != XML_FEATURE_END; xmlFeature++)
  {
    if (xmlFeature->feature == XML_FEATURE_LARGE_SIZE)
    {
      return true;
    }
  }
  return false;
}

//------------------------------------------------------------------------------
void vtkXMLParserStartElement(void* parser, const char* name, const char** atts)
{
  // Begin element handler that is registered with the XML_Parser.
  // This just casts the user data to a vtkXMLParser and calls
  // StartElement.
  static_cast<vtkXMLParser*>(parser)->StartElement(name, atts);
}

//------------------------------------------------------------------------------
void vtkXMLParserEndElement(void* parser, const char* name)
{
  // End element handler that is registered with the XML_Parser.  This
  // just casts the user data to a vtkXMLParser and calls EndElement.
  static_cast<vtkXMLParser*>(parser)->EndElement(name);
}
VTK_ABI_NAMESPACE_END
