| // 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/cfx_xmlnode.h" | |
| #include <vector> | |
| #include "core/fxcrt/fx_codepage.h" | |
| #include "core/fxcrt/xml/cfx_xmlchardata.h" | |
| #include "core/fxcrt/xml/cfx_xmlelement.h" | |
| #include "core/fxcrt/xml/cfx_xmlinstruction.h" | |
| #include "core/fxcrt/xml/cfx_xmltext.h" | |
| #include "third_party/base/stl_util.h" | |
| CFX_XMLNode::CFX_XMLNode() | |
| : m_pParent(nullptr), | |
| m_pChild(nullptr), | |
| m_pPrior(nullptr), | |
| m_pNext(nullptr) {} | |
| FX_XMLNODETYPE CFX_XMLNode::GetType() const { | |
| return FX_XMLNODE_Unknown; | |
| } | |
| CFX_XMLNode::~CFX_XMLNode() { | |
| DeleteChildren(); | |
| } | |
| void CFX_XMLNode::DeleteChildren() { | |
| CFX_XMLNode* pChild = m_pChild; | |
| while (pChild) { | |
| CFX_XMLNode* pNext = pChild->m_pNext; | |
| delete pChild; | |
| pChild = pNext; | |
| } | |
| m_pChild = nullptr; | |
| } | |
| int32_t CFX_XMLNode::CountChildNodes() const { | |
| int32_t iCount = 0; | |
| CFX_XMLNode* pChild = m_pChild; | |
| while (pChild) { | |
| iCount++; | |
| pChild = pChild->m_pNext; | |
| } | |
| return iCount; | |
| } | |
| CFX_XMLNode* CFX_XMLNode::GetChildNode(int32_t index) const { | |
| CFX_XMLNode* pChild = m_pChild; | |
| while (pChild) { | |
| if (index == 0) { | |
| return pChild; | |
| } | |
| index--; | |
| pChild = pChild->m_pNext; | |
| } | |
| return nullptr; | |
| } | |
| int32_t CFX_XMLNode::GetChildNodeIndex(CFX_XMLNode* pNode) const { | |
| int32_t index = 0; | |
| CFX_XMLNode* pChild = m_pChild; | |
| while (pChild) { | |
| if (pChild == pNode) { | |
| return index; | |
| } | |
| index++; | |
| pChild = pChild->m_pNext; | |
| } | |
| return -1; | |
| } | |
| CFX_XMLNode* CFX_XMLNode::GetPath(const wchar_t* pPath, | |
| int32_t iLength, | |
| bool bQualifiedName) const { | |
| ASSERT(pPath); | |
| if (iLength < 0) { | |
| iLength = FXSYS_wcslen(pPath); | |
| } | |
| if (iLength == 0) { | |
| return nullptr; | |
| } | |
| CFX_WideString csPath; | |
| const wchar_t* pStart = pPath; | |
| const wchar_t* pEnd = pPath + iLength; | |
| wchar_t ch; | |
| while (pStart < pEnd) { | |
| ch = *pStart++; | |
| if (ch == L'/') { | |
| break; | |
| } else { | |
| csPath += ch; | |
| } | |
| } | |
| iLength -= pStart - pPath; | |
| CFX_XMLNode* pFind = nullptr; | |
| if (csPath.GetLength() < 1) { | |
| pFind = GetNodeItem(CFX_XMLNode::Root); | |
| } else if (csPath.Compare(L"..") == 0) { | |
| pFind = m_pParent; | |
| } else if (csPath.Compare(L".") == 0) { | |
| pFind = (CFX_XMLNode*)this; | |
| } else { | |
| CFX_WideString wsTag; | |
| CFX_XMLNode* pNode = m_pChild; | |
| while (pNode) { | |
| if (pNode->GetType() == FX_XMLNODE_Element) { | |
| if (bQualifiedName) | |
| wsTag = static_cast<CFX_XMLElement*>(pNode)->GetName(); | |
| else | |
| wsTag = static_cast<CFX_XMLElement*>(pNode)->GetLocalTagName(); | |
| if (wsTag.Compare(csPath) == 0) { | |
| if (iLength < 1) | |
| pFind = pNode; | |
| else | |
| pFind = pNode->GetPath(pStart, iLength, bQualifiedName); | |
| if (pFind) | |
| return pFind; | |
| } | |
| } | |
| pNode = pNode->m_pNext; | |
| } | |
| } | |
| if (!pFind || iLength < 1) | |
| return pFind; | |
| return pFind->GetPath(pStart, iLength, bQualifiedName); | |
| } | |
| int32_t CFX_XMLNode::InsertChildNode(CFX_XMLNode* pNode, int32_t index) { | |
| pNode->m_pParent = this; | |
| if (!m_pChild) { | |
| m_pChild = pNode; | |
| pNode->m_pPrior = nullptr; | |
| pNode->m_pNext = nullptr; | |
| return 0; | |
| } | |
| if (index == 0) { | |
| pNode->m_pNext = m_pChild; | |
| pNode->m_pPrior = nullptr; | |
| m_pChild->m_pPrior = pNode; | |
| m_pChild = pNode; | |
| return 0; | |
| } | |
| int32_t iCount = 0; | |
| CFX_XMLNode* pFind = m_pChild; | |
| while (++iCount != index && pFind->m_pNext) { | |
| pFind = pFind->m_pNext; | |
| } | |
| pNode->m_pPrior = pFind; | |
| pNode->m_pNext = pFind->m_pNext; | |
| if (pFind->m_pNext) | |
| pFind->m_pNext->m_pPrior = pNode; | |
| pFind->m_pNext = pNode; | |
| return iCount; | |
| } | |
| void CFX_XMLNode::RemoveChildNode(CFX_XMLNode* pNode) { | |
| ASSERT(m_pChild && pNode); | |
| if (m_pChild == pNode) { | |
| m_pChild = pNode->m_pNext; | |
| } else { | |
| pNode->m_pPrior->m_pNext = pNode->m_pNext; | |
| } | |
| if (pNode->m_pNext) | |
| pNode->m_pNext->m_pPrior = pNode->m_pPrior; | |
| pNode->m_pParent = nullptr; | |
| pNode->m_pNext = nullptr; | |
| pNode->m_pPrior = nullptr; | |
| } | |
| CFX_XMLNode* CFX_XMLNode::GetNodeItem(CFX_XMLNode::NodeItem eItem) const { | |
| switch (eItem) { | |
| case CFX_XMLNode::Root: { | |
| CFX_XMLNode* pParent = (CFX_XMLNode*)this; | |
| while (pParent->m_pParent) { | |
| pParent = pParent->m_pParent; | |
| } | |
| return pParent; | |
| } | |
| case CFX_XMLNode::Parent: | |
| return m_pParent; | |
| case CFX_XMLNode::FirstSibling: { | |
| CFX_XMLNode* pItem = (CFX_XMLNode*)this; | |
| while (pItem->m_pPrior) { | |
| pItem = pItem->m_pPrior; | |
| } | |
| return pItem == (CFX_XMLNode*)this ? nullptr : pItem; | |
| } | |
| case CFX_XMLNode::PriorSibling: | |
| return m_pPrior; | |
| case CFX_XMLNode::NextSibling: | |
| return m_pNext; | |
| case CFX_XMLNode::LastSibling: { | |
| CFX_XMLNode* pItem = (CFX_XMLNode*)this; | |
| while (pItem->m_pNext) | |
| pItem = pItem->m_pNext; | |
| return pItem == (CFX_XMLNode*)this ? nullptr : pItem; | |
| } | |
| case CFX_XMLNode::FirstNeighbor: { | |
| CFX_XMLNode* pParent = (CFX_XMLNode*)this; | |
| while (pParent->m_pParent) | |
| pParent = pParent->m_pParent; | |
| return pParent == (CFX_XMLNode*)this ? nullptr : pParent; | |
| } | |
| case CFX_XMLNode::PriorNeighbor: { | |
| if (!m_pPrior) | |
| return m_pParent; | |
| CFX_XMLNode* pItem = m_pPrior; | |
| while (pItem->m_pChild) { | |
| pItem = pItem->m_pChild; | |
| while (pItem->m_pNext) | |
| pItem = pItem->m_pNext; | |
| } | |
| return pItem; | |
| } | |
| case CFX_XMLNode::NextNeighbor: { | |
| if (m_pChild) | |
| return m_pChild; | |
| if (m_pNext) | |
| return m_pNext; | |
| CFX_XMLNode* pItem = m_pParent; | |
| while (pItem) { | |
| if (pItem->m_pNext) | |
| return pItem->m_pNext; | |
| pItem = pItem->m_pParent; | |
| } | |
| return nullptr; | |
| } | |
| case CFX_XMLNode::LastNeighbor: { | |
| CFX_XMLNode* pItem = (CFX_XMLNode*)this; | |
| while (pItem->m_pParent) { | |
| pItem = pItem->m_pParent; | |
| } | |
| while (true) { | |
| while (pItem->m_pNext) | |
| pItem = pItem->m_pNext; | |
| if (!pItem->m_pChild) | |
| break; | |
| pItem = pItem->m_pChild; | |
| } | |
| return pItem == (CFX_XMLNode*)this ? nullptr : pItem; | |
| } | |
| case CFX_XMLNode::FirstChild: | |
| return m_pChild; | |
| case CFX_XMLNode::LastChild: { | |
| if (!m_pChild) | |
| return nullptr; | |
| CFX_XMLNode* pChild = m_pChild; | |
| while (pChild->m_pNext) | |
| pChild = pChild->m_pNext; | |
| return pChild; | |
| } | |
| default: | |
| break; | |
| } | |
| return nullptr; | |
| } | |
| int32_t CFX_XMLNode::GetNodeLevel() const { | |
| int32_t iLevel = 0; | |
| const CFX_XMLNode* pItem = m_pParent; | |
| while (pItem) { | |
| iLevel++; | |
| pItem = pItem->m_pParent; | |
| } | |
| return iLevel; | |
| } | |
| bool CFX_XMLNode::InsertNodeItem(CFX_XMLNode::NodeItem eItem, | |
| CFX_XMLNode* pNode) { | |
| switch (eItem) { | |
| case CFX_XMLNode::NextSibling: { | |
| pNode->m_pParent = m_pParent; | |
| pNode->m_pNext = m_pNext; | |
| pNode->m_pPrior = this; | |
| if (m_pNext) { | |
| m_pNext->m_pPrior = pNode; | |
| } | |
| m_pNext = pNode; | |
| return true; | |
| } | |
| case CFX_XMLNode::PriorSibling: { | |
| pNode->m_pParent = m_pParent; | |
| pNode->m_pNext = this; | |
| pNode->m_pPrior = m_pPrior; | |
| if (m_pPrior) { | |
| m_pPrior->m_pNext = pNode; | |
| } else if (m_pParent) { | |
| m_pParent->m_pChild = pNode; | |
| } | |
| m_pPrior = pNode; | |
| return true; | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| CFX_XMLNode* CFX_XMLNode::RemoveNodeItem(CFX_XMLNode::NodeItem eItem) { | |
| CFX_XMLNode* pNode = nullptr; | |
| switch (eItem) { | |
| case CFX_XMLNode::NextSibling: | |
| if (m_pNext) { | |
| pNode = m_pNext; | |
| m_pNext = pNode->m_pNext; | |
| if (m_pNext) { | |
| m_pNext->m_pPrior = this; | |
| } | |
| pNode->m_pParent = nullptr; | |
| pNode->m_pNext = nullptr; | |
| pNode->m_pPrior = nullptr; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return pNode; | |
| } | |
| std::unique_ptr<CFX_XMLNode> CFX_XMLNode::Clone() { | |
| return nullptr; | |
| } | |
| void CFX_XMLNode::SaveXMLNode( | |
| const CFX_RetainPtr<CFX_SeekableStreamProxy>& pXMLStream) { | |
| CFX_XMLNode* pNode = (CFX_XMLNode*)this; | |
| switch (pNode->GetType()) { | |
| case FX_XMLNODE_Instruction: { | |
| CFX_WideString ws; | |
| CFX_XMLInstruction* pInstruction = (CFX_XMLInstruction*)pNode; | |
| if (pInstruction->GetName().CompareNoCase(L"xml") == 0) { | |
| ws = L"<?xml version=\"1.0\" encoding=\""; | |
| uint16_t wCodePage = pXMLStream->GetCodePage(); | |
| if (wCodePage == FX_CODEPAGE_UTF16LE) { | |
| ws += L"UTF-16"; | |
| } else if (wCodePage == FX_CODEPAGE_UTF16BE) { | |
| ws += L"UTF-16be"; | |
| } else { | |
| ws += L"UTF-8"; | |
| } | |
| ws += L"\"?>"; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| } else { | |
| ws.Format(L"<?%s", pInstruction->GetName().c_str()); | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| for (auto it : pInstruction->GetAttributes()) { | |
| CFX_WideString wsValue = it.second; | |
| wsValue.Replace(L"&", L"&"); | |
| wsValue.Replace(L"<", L"<"); | |
| wsValue.Replace(L">", L">"); | |
| wsValue.Replace(L"\'", L"'"); | |
| wsValue.Replace(L"\"", L"""); | |
| ws = L" "; | |
| ws += it.first; | |
| ws += L"=\""; | |
| ws += wsValue; | |
| ws += L"\""; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| } | |
| for (auto target : pInstruction->GetTargetData()) { | |
| ws = L" \""; | |
| ws += target; | |
| ws += L"\""; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| } | |
| ws = L"?>"; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| } | |
| break; | |
| } | |
| case FX_XMLNODE_Element: { | |
| CFX_WideString ws; | |
| ws = L"<"; | |
| ws += static_cast<CFX_XMLElement*>(pNode)->GetName(); | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| for (auto it : static_cast<CFX_XMLElement*>(pNode)->GetAttributes()) { | |
| CFX_WideString wsValue = it.second; | |
| wsValue.Replace(L"&", L"&"); | |
| wsValue.Replace(L"<", L"<"); | |
| wsValue.Replace(L">", L">"); | |
| wsValue.Replace(L"\'", L"'"); | |
| wsValue.Replace(L"\"", L"""); | |
| ws = L" "; | |
| ws += it.first; | |
| ws += L"=\""; | |
| ws += wsValue; | |
| ws += L"\""; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| } | |
| if (pNode->m_pChild) { | |
| ws = L"\n>"; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| CFX_XMLNode* pChild = pNode->m_pChild; | |
| while (pChild) { | |
| pChild->SaveXMLNode(pXMLStream); | |
| pChild = pChild->m_pNext; | |
| } | |
| ws = L"</"; | |
| ws += static_cast<CFX_XMLElement*>(pNode)->GetName(); | |
| ws += L"\n>"; | |
| } else { | |
| ws = L"\n/>"; | |
| } | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| break; | |
| } | |
| case FX_XMLNODE_Text: { | |
| CFX_WideString ws = static_cast<CFX_XMLText*>(pNode)->GetText(); | |
| ws.Replace(L"&", L"&"); | |
| ws.Replace(L"<", L"<"); | |
| ws.Replace(L">", L">"); | |
| ws.Replace(L"\'", L"'"); | |
| ws.Replace(L"\"", L"""); | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| break; | |
| } | |
| case FX_XMLNODE_CharData: { | |
| CFX_WideString ws = L"<![CDATA["; | |
| ws += static_cast<CFX_XMLCharData*>(pNode)->GetText(); | |
| ws += L"]]>"; | |
| pXMLStream->WriteString(ws.AsStringC()); | |
| break; | |
| } | |
| case FX_XMLNODE_Unknown: | |
| default: | |
| break; | |
| } | |
| } |