blob: 57e19b464ee51a2afd4693449cdd8dc9ec85e62f [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "rl.h"
#include <objbase.h>
#include <XmlLite.h>
#define CHECKHR(x) {hr = x; if (FAILED(hr)) goto CleanUp;}
#define SAFERELEASE(p) {if (p) {(p)->Release(); p = NULL;}}
namespace Xml
{
Node * Node::TopNode;
//-----------------------------------------------------------------------------
//
// Description:
//
// Constructor for Attribute class.
//
//-----------------------------------------------------------------------------
Attribute::Attribute
(
Char * name,
Char * value
)
: Name(name)
, Value(value)
, Next(NULL)
{}
Char *
Attribute::GetValue
(
const Char * name
)
{
for (Attribute * p = this; p != NULL; p = p->Next)
{
if (strcmp(p->Name, name) == 0)
{
return p->Value;
}
}
return NULL;
}
void
Attribute::Dump()
{
for (Attribute * attr = this;
attr != NULL;
attr = attr->Next)
{
printf("%s=\"%s\"", attr->Name, attr->Value);
if (attr->Next != NULL)
{
printf(" ");
}
}
printf("\n");
}
//-----------------------------------------------------------------------------
//
// Description:
//
// Constructor for Node class.
//
//
//-----------------------------------------------------------------------------
Node::Node
(
const Char * name,
Attribute * attributeList
)
: Name(name)
, AttributeList(attributeList)
, Next(NULL)
, ChildList(NULL)
, Data(NULL)
// , LineNumber(Xml::LineNumber)
{}
Node *
Node::GetChild
(
const Char * name
)
{
for (Node * p = this->ChildList; p != NULL; p = p->Next)
{
if (strcmp(p->Name, name) == 0)
{
return p;
}
}
return NULL;
}
Char *
Node::GetAttributeValue
(
const Char * name
)
{
return this->AttributeList->GetValue(name);
}
void
Node::Dump
(
int indent
)
{
for (int i = 0; i < indent; i++)
{
printf(" ");
}
printf("Node %s ", this->Name);
this->AttributeList->Dump();
if (this->Data != NULL)
{
for (int i = 0; i <= indent; i++)
{
printf(" ");
}
printf("Data: %s\n", this->Data);
}
else
{
for (Node * child = this->ChildList;
child != NULL;
child = child->Next)
{
child->Dump(indent + 1);
}
}
}
void
Node::Dump()
{
this->Dump(0);
}
Char *
ConvertWCHAR
(
const WCHAR * pWchar
)
{
int len = ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pWchar, -1,
NULL, 0, NULL, NULL);
Char * newStr = new char[len + 1];
::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pWchar, -1,
newStr, len + 1, NULL, NULL);
return newStr;
}
HRESULT
ParseAttributes
(
IXmlReader * pReader,
Attribute ** ppAttrList
)
{
HRESULT hr;
const WCHAR * tmpString;
Attribute * attrLast = nullptr;
while (S_OK == (hr = pReader->MoveToNextAttribute()))
{
pReader->GetLocalName(&tmpString, nullptr);
Char * attrName = ConvertWCHAR(tmpString);
pReader->GetValue(&tmpString, nullptr);
Char * attrValue = ConvertWCHAR(tmpString);
Attribute * attrItem = new Attribute(attrName, attrValue);
if (attrLast != nullptr)
{
attrLast->Next = attrItem;
}
else
{
*ppAttrList = attrItem;
}
attrLast = attrItem;
}
return hr;
}
HRESULT
ParseNode
(
IXmlReader * pReader,
Node ** ppNode
)
{
HRESULT hr;
XmlNodeType nodeType;
Char * nodeName = nullptr;
Attribute * attrList = nullptr;
Node * childList = nullptr;
Node * childLast = nullptr;
const WCHAR * tmpString;
#define APPEND_CHILD(childNode) \
if (childLast == nullptr) \
{ \
childList = childLast = childNode; \
} \
else \
{ \
childLast->Next = childNode; \
childLast = childNode; \
}
// This call won't fail we make sure the reader is positioned at a valid
// node before ParseNode() is called.
pReader->GetNodeType(&nodeType);
do
{
switch (nodeType)
{
case XmlNodeType_Element:
{
bool inOpenElement = nodeName != nullptr;
if (inOpenElement)
{
Node * childNode;
hr = ParseNode(pReader, &childNode);
if (hr == S_OK)
{
APPEND_CHILD(childNode);
}
else
{
return hr;
}
}
else
{
pReader->GetLocalName(&tmpString, nullptr);
nodeName = ConvertWCHAR(tmpString);
hr = ParseAttributes(pReader, &attrList);
if (FAILED(hr))
{
*ppNode = nullptr;
return hr;
}
*ppNode = new Node(nodeName, attrList);
if (pReader->IsEmptyElement())
{
return S_OK;
}
}
break;
}
case XmlNodeType_EndElement:
{
Node * node = *ppNode;
// If we have a single child with data called "#text", then pull the data up to this node.
if (childList != nullptr
&& childList == childLast
&& (childList->Data != nullptr)
&& (_stricmp(childList->Name, "#text") == 0))
{
node->Data = childList->Data;
node->ChildList = nullptr;
}
else
{
node->ChildList = childList;
}
return S_OK;
}
case XmlNodeType_Attribute:
// Need to manually move to attributes when at an element node.
break;
case XmlNodeType_CDATA:
case XmlNodeType_Text:
{
pReader->GetValue(&tmpString, nullptr);
Node * node = new Node("#text", nullptr);
node->Data = ConvertWCHAR(tmpString);
APPEND_CHILD(node);
break;
}
case XmlNodeType_Comment:
case XmlNodeType_DocumentType:
case XmlNodeType_None:
case XmlNodeType_ProcessingInstruction:
case XmlNodeType_Whitespace:
case XmlNodeType_XmlDeclaration:
// Ignored.
break;
}
}
while (S_OK == (hr = pReader->Read(&nodeType)));
return hr;
#undef APPEND_CHILD
}
HRESULT
ParseXml
(
IXmlReader * pReader,
Node ** ppNode
)
{
HRESULT hr;
XmlNodeType nodeType;
// ParseNode() ignores the XML declaration node, so there can be only one
// top level node.
if (SUCCEEDED(hr = pReader->Read(&nodeType)))
{
return ParseNode(pReader, ppNode);
}
return hr;
}
HRESULT
CreateStreamOnHandle
(
HANDLE handle,
IStream ** ppStream
)
{
// Note that this function reads the whole file into memory.
//
// There is no API on ARM similar to SHCreateStreamOnFileEx which creates
// an IStream object that reads a file lazily. Rather than writing our own
// IStream implementation that does this, we just read the whole file here
// given that XML files don't get quite large and it should be okay to keep
// everything in memory.
DWORD fileSize, fileSizeHigh, bytesRead;
HGLOBAL buffer;
fileSize = GetFileSize(handle, &fileSizeHigh);
if (fileSize == INVALID_FILE_SIZE || fileSizeHigh != 0)
{
return E_FAIL;
}
buffer = GlobalAlloc(GPTR, fileSize + 1);
if (buffer == nullptr)
{
return E_FAIL;
}
if (!::ReadFile(handle, buffer, fileSize, &bytesRead, nullptr)
|| FAILED(CreateStreamOnHGlobal(buffer, /* fDeleteOnRelease */ true, ppStream)))
{
GlobalFree(buffer);
return E_FAIL;
}
return S_OK;
}
Node *
ReadFile
(
const char * fileName
)
{
IStream * pStream;
IXmlReader * pReader;
HANDLE fileHandle = CreateFile(fileName, FILE_GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (fileHandle == INVALID_HANDLE_VALUE)
{
Fatal("Cannot open XML file %s", fileName);
}
if (FAILED(CreateStreamOnHandle(fileHandle, &pStream)))
{
Fatal("Cannot create stream from file");
}
if (FAILED(CreateXmlReader(__uuidof(IXmlReader), (void**) &pReader, nullptr)))
{
Fatal("Cannot create XML reader");
}
if (FAILED(pReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit)))
{
Fatal("Cannot prohibit DTD processing");
}
if (FAILED(pReader->SetInput(pStream)))
{
Fatal("Cannot set XML reader input");
}
Node * topNode;
if (FAILED(ParseXml(pReader, &topNode)))
{
unsigned int line, linePos;
pReader->GetLineNumber(&line);
pReader->GetLinePosition(&linePos);
fprintf(
stderr,
"Error on line %d, position %d in \"%s\".\n",
line,
linePos,
fileName);
Fatal("Error parsing XML");
}
SAFERELEASE(pReader);
SAFERELEASE(pStream);
CloseHandle(fileHandle);
return topNode;
}
} // namespace Xml