| // Copyright 2014 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 "xfa/fxfa/parser/xfa_utils.h" | |
| #include <algorithm> | |
| #include <vector> | |
| #include "core/fxcrt/cfx_memorystream.h" | |
| #include "core/fxcrt/cfx_widetextbuf.h" | |
| #include "core/fxcrt/fx_codepage.h" | |
| #include "core/fxcrt/fx_extension.h" | |
| #include "core/fxcrt/xml/cfx_xmlchardata.h" | |
| #include "core/fxcrt/xml/cfx_xmlelement.h" | |
| #include "core/fxcrt/xml/cfx_xmlnode.h" | |
| #include "core/fxcrt/xml/cfx_xmltext.h" | |
| #include "fxjs/xfa/cjx_object.h" | |
| #include "third_party/base/check.h" | |
| #include "third_party/base/stl_util.h" | |
| #include "xfa/fxfa/parser/cxfa_document.h" | |
| #include "xfa/fxfa/parser/cxfa_localemgr.h" | |
| #include "xfa/fxfa/parser/cxfa_localevalue.h" | |
| #include "xfa/fxfa/parser/cxfa_measurement.h" | |
| #include "xfa/fxfa/parser/cxfa_node.h" | |
| #include "xfa/fxfa/parser/cxfa_ui.h" | |
| #include "xfa/fxfa/parser/cxfa_value.h" | |
| #include "xfa/fxfa/parser/xfa_basic_data.h" | |
| namespace { | |
| const char kFormNS[] = "http://www.xfa.org/schema/xfa-form/"; | |
| WideString ExportEncodeAttribute(const WideString& str) { | |
| CFX_WideTextBuf textBuf; | |
| int32_t iLen = str.GetLength(); | |
| for (int32_t i = 0; i < iLen; i++) { | |
| switch (str[i]) { | |
| case '&': | |
| textBuf << "&"; | |
| break; | |
| case '<': | |
| textBuf << "<"; | |
| break; | |
| case '>': | |
| textBuf << ">"; | |
| break; | |
| case '\'': | |
| textBuf << "'"; | |
| break; | |
| case '\"': | |
| textBuf << """; | |
| break; | |
| default: | |
| textBuf.AppendChar(str[i]); | |
| } | |
| } | |
| return textBuf.MakeString(); | |
| } | |
| bool IsXMLValidChar(wchar_t ch) { | |
| return ch == 0x09 || ch == 0x0A || ch == 0x0D || | |
| (ch >= 0x20 && ch <= 0xD7FF) || (ch >= 0xE000 && ch <= 0xFFFD); | |
| } | |
| WideString ExportEncodeContent(const WideString& str) { | |
| CFX_WideTextBuf textBuf; | |
| int32_t iLen = str.GetLength(); | |
| for (int32_t i = 0; i < iLen; i++) { | |
| wchar_t ch = str[i]; | |
| if (!IsXMLValidChar(ch)) | |
| continue; | |
| if (ch == '&') { | |
| textBuf << "&"; | |
| } else if (ch == '<') { | |
| textBuf << "<"; | |
| } else if (ch == '>') { | |
| textBuf << ">"; | |
| } else if (ch == '\'') { | |
| textBuf << "'"; | |
| } else if (ch == '\"') { | |
| textBuf << """; | |
| } else if (ch == ' ') { | |
| if (i && str[i - 1] != ' ') { | |
| textBuf.AppendChar(' '); | |
| } else { | |
| textBuf << " "; | |
| } | |
| } else { | |
| textBuf.AppendChar(str[i]); | |
| } | |
| } | |
| return textBuf.MakeString(); | |
| } | |
| bool AttributeSaveInDataModel(CXFA_Node* pNode, XFA_Attribute eAttribute) { | |
| bool bSaveInDataModel = false; | |
| if (pNode->GetElementType() != XFA_Element::Image) | |
| return bSaveInDataModel; | |
| CXFA_Node* pValueNode = pNode->GetParent(); | |
| if (!pValueNode || pValueNode->GetElementType() != XFA_Element::Value) | |
| return bSaveInDataModel; | |
| CXFA_Node* pFieldNode = pValueNode->GetParent(); | |
| if (pFieldNode && pFieldNode->GetBindData() && | |
| eAttribute == XFA_Attribute::Href) { | |
| bSaveInDataModel = true; | |
| } | |
| return bSaveInDataModel; | |
| } | |
| bool ContentNodeNeedtoExport(CXFA_Node* pContentNode) { | |
| Optional<WideString> wsContent = | |
| pContentNode->JSObject()->TryContent(false, false); | |
| if (!wsContent) | |
| return false; | |
| DCHECK(pContentNode->IsContentNode()); | |
| CXFA_Node* pParentNode = pContentNode->GetParent(); | |
| if (!pParentNode || pParentNode->GetElementType() != XFA_Element::Value) | |
| return true; | |
| CXFA_Node* pGrandParentNode = pParentNode->GetParent(); | |
| if (!pGrandParentNode || !pGrandParentNode->IsContainerNode()) | |
| return true; | |
| if (!pGrandParentNode->GetBindData()) | |
| return false; | |
| if (pGrandParentNode->GetFFWidgetType() == XFA_FFWidgetType::kPasswordEdit) | |
| return false; | |
| return true; | |
| } | |
| void SaveAttribute(CXFA_Node* pNode, | |
| XFA_Attribute eName, | |
| const WideString& wsName, | |
| bool bProto, | |
| WideString& wsOutput) { | |
| if (!bProto && !pNode->JSObject()->HasAttribute(eName)) | |
| return; | |
| Optional<WideString> value = pNode->JSObject()->TryAttribute(eName, false); | |
| if (!value) | |
| return; | |
| wsOutput += L" "; | |
| wsOutput += wsName; | |
| wsOutput += L"=\""; | |
| wsOutput += ExportEncodeAttribute(*value); | |
| wsOutput += L"\""; | |
| } | |
| void RegenerateFormFile_Changed(CXFA_Node* pNode, | |
| CFX_WideTextBuf& buf, | |
| bool bSaveXML) { | |
| WideString wsAttrs; | |
| for (size_t i = 0;; ++i) { | |
| XFA_Attribute attr = pNode->GetAttribute(i); | |
| if (attr == XFA_Attribute::Unknown) | |
| break; | |
| if (attr == XFA_Attribute::Name || | |
| (AttributeSaveInDataModel(pNode, attr) && !bSaveXML)) { | |
| continue; | |
| } | |
| WideString wsAttr; | |
| SaveAttribute(pNode, attr, WideString::FromASCII(XFA_AttributeToName(attr)), | |
| bSaveXML, wsAttr); | |
| wsAttrs += wsAttr; | |
| } | |
| WideString wsChildren; | |
| switch (pNode->GetObjectType()) { | |
| case XFA_ObjectType::ContentNode: { | |
| if (!bSaveXML && !ContentNodeNeedtoExport(pNode)) | |
| break; | |
| CXFA_Node* pRawValueNode = pNode->GetFirstChild(); | |
| while (pRawValueNode && | |
| pRawValueNode->GetElementType() != XFA_Element::SharpxHTML && | |
| pRawValueNode->GetElementType() != XFA_Element::Sharptext && | |
| pRawValueNode->GetElementType() != XFA_Element::Sharpxml) { | |
| pRawValueNode = pRawValueNode->GetNextSibling(); | |
| } | |
| if (!pRawValueNode) | |
| break; | |
| Optional<WideString> contentType = | |
| pNode->JSObject()->TryAttribute(XFA_Attribute::ContentType, false); | |
| if (pRawValueNode->GetElementType() == XFA_Element::SharpxHTML && | |
| contentType.has_value() && | |
| contentType.value().EqualsASCII("text/html")) { | |
| CFX_XMLNode* pExDataXML = pNode->GetXMLMappingNode(); | |
| if (!pExDataXML) | |
| break; | |
| CFX_XMLNode* pRichTextXML = pExDataXML->GetFirstChild(); | |
| if (!pRichTextXML) | |
| break; | |
| auto pMemStream = pdfium::MakeRetain<CFX_MemoryStream>(); | |
| pRichTextXML->Save(pMemStream); | |
| wsChildren += WideString::FromUTF8( | |
| ByteStringView(pMemStream->GetBuffer(), pMemStream->GetSize())); | |
| } else if (pRawValueNode->GetElementType() == XFA_Element::Sharpxml && | |
| contentType.has_value() && | |
| contentType.value().EqualsASCII("text/xml")) { | |
| Optional<WideString> rawValue = pRawValueNode->JSObject()->TryAttribute( | |
| XFA_Attribute::Value, false); | |
| if (!rawValue || rawValue->IsEmpty()) | |
| break; | |
| std::vector<WideString> wsSelTextArray = | |
| fxcrt::Split(rawValue.value(), L'\n'); | |
| CXFA_Node* pParentNode = pNode->GetParent(); | |
| CXFA_Node* pGrandparentNode = pParentNode->GetParent(); | |
| WideString bodyTagName = | |
| pGrandparentNode->JSObject()->GetCData(XFA_Attribute::Name); | |
| if (bodyTagName.IsEmpty()) | |
| bodyTagName = L"ListBox1"; | |
| buf << "<"; | |
| buf << bodyTagName; | |
| buf << " xmlns=\"\"\n>"; | |
| for (int32_t i = 0; i < pdfium::CollectionSize<int32_t>(wsSelTextArray); | |
| i++) { | |
| buf << "<value\n>"; | |
| buf << ExportEncodeContent(wsSelTextArray[i]); | |
| buf << "</value\n>"; | |
| } | |
| buf << "</"; | |
| buf << bodyTagName; | |
| buf << "\n>"; | |
| wsChildren += buf.AsStringView(); | |
| buf.Clear(); | |
| } else { | |
| WideString wsValue = | |
| pRawValueNode->JSObject()->GetCData(XFA_Attribute::Value); | |
| wsChildren += ExportEncodeContent(wsValue); | |
| } | |
| break; | |
| } | |
| case XFA_ObjectType::TextNode: | |
| case XFA_ObjectType::NodeC: | |
| case XFA_ObjectType::NodeV: { | |
| WideString wsValue = pNode->JSObject()->GetCData(XFA_Attribute::Value); | |
| wsChildren += ExportEncodeContent(wsValue); | |
| break; | |
| } | |
| default: | |
| if (pNode->GetElementType() == XFA_Element::Items) { | |
| CXFA_Node* pTemplateNode = pNode->GetTemplateNodeIfExists(); | |
| if (!pTemplateNode || | |
| pTemplateNode->CountChildren(XFA_Element::Unknown, false) != | |
| pNode->CountChildren(XFA_Element::Unknown, false)) { | |
| bSaveXML = true; | |
| } | |
| } | |
| CFX_WideTextBuf newBuf; | |
| CXFA_Node* pChildNode = pNode->GetFirstChild(); | |
| while (pChildNode) { | |
| RegenerateFormFile_Changed(pChildNode, newBuf, bSaveXML); | |
| wsChildren += newBuf.AsStringView(); | |
| newBuf.Clear(); | |
| pChildNode = pChildNode->GetNextSibling(); | |
| } | |
| if (!bSaveXML && !wsChildren.IsEmpty() && | |
| pNode->GetElementType() == XFA_Element::Items) { | |
| wsChildren.clear(); | |
| bSaveXML = true; | |
| CXFA_Node* pChild = pNode->GetFirstChild(); | |
| while (pChild) { | |
| RegenerateFormFile_Changed(pChild, newBuf, bSaveXML); | |
| wsChildren += newBuf.AsStringView(); | |
| newBuf.Clear(); | |
| pChild = pChild->GetNextSibling(); | |
| } | |
| } | |
| break; | |
| } | |
| if (!wsChildren.IsEmpty() || !wsAttrs.IsEmpty() || | |
| pNode->JSObject()->HasAttribute(XFA_Attribute::Name)) { | |
| WideString wsElement = WideString::FromASCII(pNode->GetClassName()); | |
| WideString wsName; | |
| SaveAttribute(pNode, XFA_Attribute::Name, L"name", true, wsName); | |
| buf << "<"; | |
| buf << wsElement; | |
| buf << wsName; | |
| buf << wsAttrs; | |
| if (wsChildren.IsEmpty()) { | |
| buf << "\n/>"; | |
| } else { | |
| buf << "\n>"; | |
| buf << wsChildren; | |
| buf << "</"; | |
| buf << wsElement; | |
| buf << "\n>"; | |
| } | |
| } | |
| } | |
| void RegenerateFormFile_Container(CXFA_Node* pNode, | |
| const RetainPtr<IFX_SeekableStream>& pStream, | |
| bool bSaveXML) { | |
| XFA_Element eType = pNode->GetElementType(); | |
| if (eType == XFA_Element::Field || eType == XFA_Element::Draw || | |
| !pNode->IsContainerNode()) { | |
| CFX_WideTextBuf buf; | |
| RegenerateFormFile_Changed(pNode, buf, bSaveXML); | |
| size_t nLen = buf.GetLength(); | |
| if (nLen > 0) | |
| pStream->WriteString(buf.MakeString().ToUTF8().AsStringView()); | |
| return; | |
| } | |
| WideString wsElement = WideString::FromASCII(pNode->GetClassName()); | |
| pStream->WriteString("<"); | |
| pStream->WriteString(wsElement.ToUTF8().AsStringView()); | |
| WideString wsOutput; | |
| SaveAttribute(pNode, XFA_Attribute::Name, L"name", true, wsOutput); | |
| WideString wsAttrs; | |
| for (size_t i = 0;; ++i) { | |
| XFA_Attribute attr = pNode->GetAttribute(i); | |
| if (attr == XFA_Attribute::Unknown) | |
| break; | |
| if (attr == XFA_Attribute::Name) | |
| continue; | |
| WideString wsAttr; | |
| SaveAttribute(pNode, attr, WideString::FromASCII(XFA_AttributeToName(attr)), | |
| false, wsAttr); | |
| wsOutput += wsAttr; | |
| } | |
| if (!wsOutput.IsEmpty()) | |
| pStream->WriteString(wsOutput.ToUTF8().AsStringView()); | |
| CXFA_Node* pChildNode = pNode->GetFirstChild(); | |
| if (!pChildNode) { | |
| pStream->WriteString(" />\n"); | |
| return; | |
| } | |
| pStream->WriteString(">\n"); | |
| while (pChildNode) { | |
| RegenerateFormFile_Container(pChildNode, pStream, bSaveXML); | |
| pChildNode = pChildNode->GetNextSibling(); | |
| } | |
| pStream->WriteString("</"); | |
| pStream->WriteString(wsElement.ToUTF8().AsStringView()); | |
| pStream->WriteString(">\n"); | |
| } | |
| WideString RecognizeXFAVersionNumber(CXFA_Node* pTemplateRoot) { | |
| if (!pTemplateRoot) | |
| return WideString(); | |
| Optional<WideString> templateNS = pTemplateRoot->JSObject()->TryNamespace(); | |
| if (!templateNS) | |
| return WideString(); | |
| XFA_VERSION eVersion = | |
| pTemplateRoot->GetDocument()->RecognizeXFAVersionNumber(*templateNS); | |
| if (eVersion == XFA_VERSION_UNKNOWN) | |
| eVersion = XFA_VERSION_DEFAULT; | |
| return WideString::Format(L"%i.%i", eVersion / 100, eVersion % 100); | |
| } | |
| } // namespace | |
| CXFA_LocaleValue XFA_GetLocaleValue(CXFA_Node* pNode) { | |
| CXFA_Value* pNodeValue = | |
| pNode->GetChild<CXFA_Value>(0, XFA_Element::Value, false); | |
| if (!pNodeValue) | |
| return CXFA_LocaleValue(); | |
| CXFA_Node* pValueChild = pNodeValue->GetFirstChild(); | |
| if (!pValueChild) | |
| return CXFA_LocaleValue(); | |
| int32_t iVTType = XFA_VT_NULL; | |
| switch (pValueChild->GetElementType()) { | |
| case XFA_Element::Decimal: | |
| iVTType = XFA_VT_DECIMAL; | |
| break; | |
| case XFA_Element::Float: | |
| iVTType = XFA_VT_FLOAT; | |
| break; | |
| case XFA_Element::Date: | |
| iVTType = XFA_VT_DATE; | |
| break; | |
| case XFA_Element::Time: | |
| iVTType = XFA_VT_TIME; | |
| break; | |
| case XFA_Element::DateTime: | |
| iVTType = XFA_VT_DATETIME; | |
| break; | |
| case XFA_Element::Boolean: | |
| iVTType = XFA_VT_BOOLEAN; | |
| break; | |
| case XFA_Element::Integer: | |
| iVTType = XFA_VT_INTEGER; | |
| break; | |
| case XFA_Element::Text: | |
| iVTType = XFA_VT_TEXT; | |
| break; | |
| default: | |
| iVTType = XFA_VT_NULL; | |
| break; | |
| } | |
| return CXFA_LocaleValue(iVTType, pNode->GetRawValue(), | |
| pNode->GetDocument()->GetLocaleMgr()); | |
| } | |
| bool XFA_FDEExtension_ResolveNamespaceQualifier(CFX_XMLElement* pNode, | |
| const WideString& wsQualifier, | |
| WideString* wsNamespaceURI) { | |
| if (!pNode) | |
| return false; | |
| CFX_XMLNode* pFakeRoot = pNode->GetRoot(); | |
| WideString wsNSAttribute; | |
| bool bRet = false; | |
| if (wsQualifier.IsEmpty()) { | |
| wsNSAttribute = L"xmlns"; | |
| bRet = true; | |
| } else { | |
| wsNSAttribute = L"xmlns:" + wsQualifier; | |
| } | |
| for (CFX_XMLNode* pParent = pNode; pParent != pFakeRoot; | |
| pParent = pParent->GetParent()) { | |
| CFX_XMLElement* pElement = ToXMLElement(pParent); | |
| if (pElement && pElement->HasAttribute(wsNSAttribute)) { | |
| *wsNamespaceURI = pElement->GetAttribute(wsNSAttribute); | |
| return true; | |
| } | |
| } | |
| wsNamespaceURI->clear(); | |
| return bRet; | |
| } | |
| void XFA_DataExporter_DealWithDataGroupNode(CXFA_Node* pDataNode) { | |
| if (!pDataNode || pDataNode->GetElementType() == XFA_Element::DataValue) | |
| return; | |
| int32_t iChildNum = 0; | |
| for (CXFA_Node* pChildNode = pDataNode->GetFirstChild(); pChildNode; | |
| pChildNode = pChildNode->GetNextSibling()) { | |
| iChildNum++; | |
| XFA_DataExporter_DealWithDataGroupNode(pChildNode); | |
| } | |
| if (pDataNode->GetElementType() != XFA_Element::DataGroup) | |
| return; | |
| CFX_XMLElement* pElement = ToXMLElement(pDataNode->GetXMLMappingNode()); | |
| if (iChildNum > 0) { | |
| if (pElement->HasAttribute(L"xfa:dataNode")) | |
| pElement->RemoveAttribute(L"xfa:dataNode"); | |
| return; | |
| } | |
| pElement->SetAttribute(L"xfa:dataNode", L"dataGroup"); | |
| } | |
| void XFA_DataExporter_RegenerateFormFile( | |
| CXFA_Node* pNode, | |
| const RetainPtr<IFX_SeekableStream>& pStream, | |
| bool bSaveXML) { | |
| if (pNode->IsModelNode()) { | |
| pStream->WriteString("<form xmlns=\""); | |
| pStream->WriteString(kFormNS); | |
| WideString wsVersionNumber = RecognizeXFAVersionNumber( | |
| ToNode(pNode->GetDocument()->GetXFAObject(XFA_HASHCODE_Template))); | |
| if (wsVersionNumber.IsEmpty()) | |
| wsVersionNumber = L"2.8"; | |
| wsVersionNumber += L"/\"\n>"; | |
| pStream->WriteString(wsVersionNumber.ToUTF8().AsStringView()); | |
| CXFA_Node* pChildNode = pNode->GetFirstChild(); | |
| while (pChildNode) { | |
| RegenerateFormFile_Container(pChildNode, pStream, false); | |
| pChildNode = pChildNode->GetNextSibling(); | |
| } | |
| pStream->WriteString("</form\n>"); | |
| } else { | |
| RegenerateFormFile_Container(pNode, pStream, bSaveXML); | |
| } | |
| } | |
| bool XFA_FieldIsMultiListBox(CXFA_Node* pFieldNode) { | |
| if (!pFieldNode) | |
| return false; | |
| CXFA_Ui* pUIChild = pFieldNode->GetChild<CXFA_Ui>(0, XFA_Element::Ui, false); | |
| if (!pUIChild) | |
| return false; | |
| CXFA_Node* pFirstChild = pUIChild->GetFirstChild(); | |
| if (!pFirstChild || | |
| pFirstChild->GetElementType() != XFA_Element::ChoiceList) { | |
| return false; | |
| } | |
| return pFirstChild->JSObject()->GetEnum(XFA_Attribute::Open) == | |
| XFA_AttributeValue::MultiSelect; | |
| } | |
| int32_t XFA_MapRotation(int32_t nRotation) { | |
| nRotation = nRotation % 360; | |
| nRotation = nRotation < 0 ? nRotation + 360 : nRotation; | |
| return nRotation; | |
| } | |
| void XFA_EventErrorAccumulate(XFA_EventError* pAcc, XFA_EventError eNew) { | |
| if (*pAcc == XFA_EventError::kNotExist || eNew == XFA_EventError::kError) | |
| *pAcc = eNew; | |
| } |