You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/ScriptCanvasDeveloper/Code/Editor/Source/XMLDoc.cpp

412 lines
14 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "precompiled.h"
#include "XMLDoc.h"
#include <AzCore/XML/rapidxml_print.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace ScriptCanvasDeveloperEditor
{
namespace Internal
{
xml_node<> *FindDocumentTypeNode(const xml_document<> & doc, const AZStd::string & nodeName)
{
// this will only work if the xml document was parsed with the flag "parse_doctype_node"
for(xml_node<> *docTypeNode=doc.first_node(); docTypeNode!=nullptr; docTypeNode=docTypeNode->next_sibling())
{
if (docTypeNode->type() == node_type::node_doctype)
{
if ( nodeName == docTypeNode->value() )
{
return docTypeNode;
}
}
}
return nullptr;
}
bool GetAttribute(xml_node<> *tsNode, AZStd::string_view attribName, float & value)
{
if( tsNode != nullptr)
{
for (xml_attribute<> *attrib = tsNode->first_attribute(); attrib != nullptr; attrib = attrib->next_attribute())
{
if ( attribName == attrib->name() )
{
value = AzFramework::StringFunc::ToFloat( attrib->value() );
return true;
}
}
}
return false;
}
bool GetAttribute(xml_node<> *tsNode, AZStd::string_view attribName, AZStd::string & value)
{
if( tsNode != nullptr)
{
for (xml_attribute<> *attrib = tsNode->first_attribute(); attrib != nullptr; attrib = attrib->next_attribute())
{
if (attribName == attrib->name())
{
value = attrib->value();
return true;
}
}
}
return false;
}
xml_node<> *FindContextNode(const xml_document<> & doc, const AZStd::string & contextName)
{
xml_node<> *tsNode = doc.first_node("TS");
if (tsNode != nullptr)
{
for(xml_node<> *contextNode=tsNode->first_node("context"); contextNode!=nullptr; contextNode=contextNode->next_sibling("context") )
{
xml_node<> *contextNameNode = contextNode->first_node("name");
if (contextNameNode != nullptr)
{
if( contextName == contextNameNode->value() )
{
return contextNode;
}
}
}
}
return nullptr;
}
}
XMLDocPtr XMLDoc::Alloc(const AZStd::string& contextName)
{
XMLDocPtr xmlDoc( AZStd::make_shared<XMLDoc>() );
xmlDoc->CreateTSDoc(contextName);
return xmlDoc;
}
XMLDocPtr XMLDoc::LoadFromDisk(const AZStd::string& fileName)
{
XMLDocPtr xmlDoc(AZStd::make_shared<XMLDoc>());
if ( !xmlDoc->LoadTSDoc(fileName) )
{
xmlDoc = nullptr;
}
return xmlDoc;
}
XMLDoc::XMLDoc()
: m_tsNode(nullptr)
, m_context(nullptr)
, m_readBuffer(0)
{
}
void XMLDoc::CreateTSDoc(const AZStd::string& contextName)
{
xml_node<>* decl = m_doc.allocate_node(node_declaration);
decl->append_attribute(m_doc.allocate_attribute("version", "1.0"));
decl->append_attribute(m_doc.allocate_attribute("encoding", "utf-8"));
m_doc.append_node(decl);
xml_node<>* commentNode = m_doc.allocate_node(node_comment, "", AllocString(AZStd::string::format("Generated for %s", contextName.c_str())));
m_doc.append_node(commentNode);
xml_node<>* docType = m_doc.allocate_node(node_doctype, "", "TS");
m_doc.append_node(docType);
m_tsNode = m_doc.allocate_node(node_element, "TS");
m_doc.append_node(m_tsNode);
m_tsNode->append_attribute(m_doc.allocate_attribute("version", "2.1"));
m_tsNode->append_attribute(m_doc.allocate_attribute("language", "en_US"));
}
bool XMLDoc::LoadTSDoc(const AZStd::string& fileName)
{
bool success = false;
char tsFilePath[AZ::IO::MaxPathLength] = { "" };
if (AZ::IO::FileIOBase::GetInstance()->ResolvePath(fileName.c_str(), tsFilePath, AZ::IO::MaxPathLength))
{
AZ::IO::FileIOStream xmlFile;
if( AZ::IO::FileIOBase::GetInstance()->Exists(tsFilePath) )
{
if ( xmlFile.Open(tsFilePath, AZ::IO::OpenMode::ModeRead|AZ::IO::OpenMode::ModeText) )
{
AZ::IO::SizeType bytesToRead = xmlFile.GetLength();
if( bytesToRead > 0 )
{
m_readBuffer.resize(bytesToRead, '\0');
if( xmlFile.Read(bytesToRead, m_readBuffer.data() ) == bytesToRead )
{
m_doc.parse<parse_doctype_node|parse_declaration_node|parse_comment_nodes>( m_readBuffer.data() );
success = IsValidTSDoc();
if (success)
{
AZ_TracePrintf("ScriptCanvas", "Loaded \"%s\"", tsFilePath);
}
}
else
{
AZ_Error("ScriptCanvas", false, "XMLDoc::LoadTSDoc-Error reading Qt .ts file! filename=\"%s\".", tsFilePath);
}
}
else
{
AZ_Error("ScriptCanvas", false, "XMLDoc::LoadTSDoc-Zero byte Qt .ts file! filename=\"%s\".", tsFilePath);
}
}
else
{
AZ_Error("ScriptCanvas", false, "XMLDoc::LoadTSDoc-Can't open file Qt .ts file! filename=\"%s\".", tsFilePath);
}
}
}
else
{
AZ_Error("ScriptCanvas", false, "XMLDoc::LoadTSDoc-Invalid filename specified! filename=\"%s\".", tsFilePath);
}
return success;
}
bool XMLDoc::WriteToDisk(const AZStd::string & fileName)
{
bool success = false;
char tsFilePath[AZ::IO::MaxPathLength] = { "" };
if (AZ::IO::FileIOBase::GetInstance()->ResolvePath(fileName.c_str(), tsFilePath, AZ::IO::MaxPathLength))
{
AZStd::string writeFolder(tsFilePath);
AzFramework::StringFunc::Path::StripFullName(writeFolder);
if (!AZ::IO::FileIOBase::GetInstance()->IsDirectory(writeFolder.c_str()))
{
AZ::IO::FileIOBase::GetInstance()->CreatePath(writeFolder.c_str());
}
AZ::IO::FileIOStream xmlFile;
if (xmlFile.Open(tsFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText))
{
AZStd::string xmlData(ToString());
AZ::IO::SizeType bytesWritten = xmlFile.Write(xmlData.size(), xmlData.data());
if (bytesWritten != xmlData.size())
{
AZ_Error("ScriptCanvas", false, "Write error writing out %s, bytes actually written: %llu expected bytes to write: %llu!", tsFilePath, bytesWritten, xmlData.size());
}
else
{
AZ_TracePrintf("ScriptCanvas", "Successfully wrote out ScriptCanvas localization file \"%s\".", tsFilePath);
success = true;
}
xmlFile.Close();
}
else
{
AZ_Error("ScriptCanvas", false, "Could not open file \"%s\"!", tsFilePath);
}
}
else
{
AZ_Error("ScriptCanvas", false, "Invalid filename specified XMLDoc::WriteToDisk! filename=\"%s\".", tsFilePath);
}
return success;
}
bool XMLDoc::StartContext(const AZStd::string& contextName)
{
bool isNew = false;
if( !contextName.empty() )
{
// check to see if we have a context of this name already, if so find that node and use it, otherwise
// create a new one.
xml_node<> *existingContextNode = Internal::FindContextNode(m_doc, contextName);
if (existingContextNode == nullptr)
{
m_context = m_doc.allocate_node(node_element, "context");
m_tsNode->append_node(m_context);
xml_node<>* contextNameNode = m_doc.allocate_node(node_element, "name", AllocString(contextName));
m_context->append_node(contextNameNode);
isNew = true;
}
else
{
m_context = existingContextNode;
}
}
else
{
m_context = nullptr;
}
return isNew;
}
void XMLDoc::AddToContext(const AZStd::string& id, const AZStd::string& translation/*=""*/, const AZStd::string& comment /*=""*/, const AZStd::string& source/*=""*/)
{
if (m_context == nullptr)
{
return;
}
xml_node<>* messageNode = m_doc.allocate_node(node_element, "message");
messageNode->append_attribute(m_doc.allocate_attribute("id", AllocString(id)));
xml_node<>* sourceNode = m_doc.allocate_node(node_element, "source", AllocString(source.empty() ? id.c_str() : source.c_str()));
messageNode->append_node(sourceNode);
xml_node<>* translationNode = m_doc.allocate_node(node_element, "translation", AllocString(translation));
messageNode->append_node(translationNode);
xml_node<>* commentNode = m_doc.allocate_node(node_element, "comment", AllocString(comment));
messageNode->append_node(commentNode);
m_context->append_node(messageNode);
}
bool XMLDoc::MethodFamilyExists(const AZStd::string& baseId) const
{
if (m_tsNode != nullptr)
{
AZStd::string nameID(baseId + "_NAME");
for (xml_node<> *contextNode=m_tsNode->first_node("context"); contextNode!=nullptr; contextNode=contextNode->next_sibling("context"))
{
for (xml_node<> *messageNode = contextNode->first_node("message"); messageNode != nullptr; messageNode = messageNode->next_sibling("message"))
{
AZStd::string id;
if ( Internal::GetAttribute(messageNode, "id", id) )
{
if (id == nameID)
{
return true;
}
}
}
}
}
return false;
}
AZStd::string XMLDoc::ToString() const
{
AZStd::string buffer;
print( AZStd::back_inserter(buffer), m_doc, 0);
return buffer;
}
const char *XMLDoc::AllocString(const AZStd::string& str)
{
return m_doc.allocate_string( str.c_str(), str.size() + 1 );
}
bool XMLDoc::IsValidTSDoc()
{
bool isTSDoc = false;
//
// Basic format of a .ts file, we are checking to make sure the document type is ts, and the version is 2.1 or greater
// and there is a non-null language attribute. and at least 1 context section
//
//<?xml version="1.0" encoding="utf-8"?>
//<!DOCTYPE TS>
//<TS version="2.1" language="en_US">
// <context>
// ...
// </context>
//</TS>
//
// this node should be the doc type
xml_node<> *docTypeNode = Internal::FindDocumentTypeNode(m_doc, "TS");
if (docTypeNode !=nullptr)
{
xml_node<> *tsNode = m_doc.first_node("TS");
if( tsNode != nullptr)
{
float attribVersion = 0.0f;
if( Internal::GetAttribute(tsNode, "version", attribVersion) && (attribVersion >= 2.1f) )
{
AZStd::string attribLanguage;
if( Internal::GetAttribute(tsNode, "language", attribLanguage) && !attribLanguage.empty() )
{
xml_node<> *contextNode = tsNode->first_node("context");
if(contextNode != nullptr)
{
m_tsNode = tsNode;
AZ_TracePrintf("ScriptCanvas", "TS: Version=%2.1f, Language=\"%s\"", attribVersion, attribLanguage.c_str());
isTSDoc = true;
}
else
{
AZ_Warning("ScriptCanvas", false, "TS document contains no \"context\" nodes!");
}
}
else
{
AZ_Warning("ScriptCanvas", false, "TS document has a bad or missing \"language\" attribute!");
}
}
else
{
AZ_Warning("ScriptCanvas", false, "TS document has a bad or missing \"version\" attribute!");
}
}
else
{
AZ_Error("ScriptCanvas", false, "TS document contains no \"TS\" node!");
}
}
else
{
AZ_Error("ScriptCanvas", false, "XML doc is not a valid TS document!");
}
return isTSDoc;
}
}