| // Copyright 2014 The PDFium Authors | |
| // 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/fx_codepage.h" | |
| #include "core/fxcrt/fx_extension.h" | |
| #include "core/fxcrt/widetext_buffer.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 "xfa/fxfa/parser/cxfa_document.h" | |
| #include "xfa/fxfa/parser/cxfa_localemgr.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) { | |
| WideString textBuf; | |
| textBuf.Reserve(str.GetLength()); // Result always at least as big as input. | |
| for (size_t i = 0; i < str.GetLength(); i++) { | |
| switch (str[i]) { | |
| case '&': | |
| textBuf += L"&"; | |
| break; | |
| case '<': | |
| textBuf += L"<"; | |
| break; | |
| case '>': | |
| textBuf += L">"; | |
| break; | |
| case '\'': | |
| textBuf += L"'"; | |
| break; | |
| case '\"': | |
| textBuf += L"""; | |
| break; | |
| default: | |
| textBuf += str[i]; | |
| } | |
| } | |
| return textBuf; | |
| } | |
| 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) { | |
| WideTextBuffer textBuf; | |
| size_t iLen = str.GetLength(); | |
| for (size_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) { | |
| absl::optional<WideString> wsContent = | |
| pContentNode->JSObject()->TryContent(false, false); | |
| if (!wsContent.has_value()) | |
| 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; | |
| } | |
| WideString SaveAttribute(CXFA_Node* pNode, | |
| XFA_Attribute eName, | |
| WideStringView wsName, | |
| bool bProto) { | |
| if (!bProto && !pNode->JSObject()->HasAttribute(eName)) | |
| return WideString(); | |
| absl::optional<WideString> value = | |
| pNode->JSObject()->TryAttribute(eName, false); | |
| if (!value.has_value()) | |
| return WideString(); | |
| WideString wsEncoded = ExportEncodeAttribute(value.value()); | |
| return WideString{L" ", wsName, L"=\"", wsEncoded.AsStringView(), L"\""}; | |
| } | |
| void RegenerateFormFile_Changed(CXFA_Node* pNode, | |
| WideTextBuffer& 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 = WideString::FromASCII(XFA_AttributeToName(attr)); | |
| wsAttrs += SaveAttribute(pNode, attr, wsAttr.AsStringView(), bSaveXML); | |
| } | |
| 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; | |
| absl::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->GetSpan())); | |
| } else if (pRawValueNode->GetElementType() == XFA_Element::Sharpxml && | |
| contentType.has_value() && | |
| contentType.value().EqualsASCII("text/xml")) { | |
| absl::optional<WideString> rawValue = | |
| pRawValueNode->JSObject()->TryAttribute(XFA_Attribute::Value, | |
| false); | |
| if (!rawValue.has_value() || 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 << "<" << bodyTagName << " xmlns=\"\">\n"; | |
| for (const WideString& text : wsSelTextArray) | |
| buf << "<value>" << ExportEncodeContent(text) << "</value>\n"; | |
| buf << "</" << bodyTagName << ">\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; | |
| } | |
| } | |
| WideTextBuffer 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()); | |
| buf << "<"; | |
| buf << wsElement; | |
| buf << SaveAttribute(pNode, XFA_Attribute::Name, L"name", true); | |
| buf << wsAttrs; | |
| if (wsChildren.IsEmpty()) { | |
| buf << "/>\n"; | |
| } else { | |
| buf << ">\n" << wsChildren << "</" << wsElement << ">\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()) { | |
| WideTextBuffer 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); | |
| 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 = WideString::FromASCII(XFA_AttributeToName(attr)); | |
| wsOutput += SaveAttribute(pNode, attr, wsAttr.AsStringView(), false); | |
| } | |
| 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(); | |
| absl::optional<WideString> templateNS = | |
| pTemplateRoot->JSObject()->TryNamespace(); | |
| if (!templateNS.has_value()) | |
| return WideString(); | |
| XFA_VERSION eVersion = | |
| pTemplateRoot->GetDocument()->RecognizeXFAVersionNumber( | |
| templateNS.value()); | |
| if (eVersion == XFA_VERSION_UNKNOWN) | |
| eVersion = XFA_VERSION_DEFAULT; | |
| return WideString::Format(L"%i.%i", eVersion / 100, eVersion % 100); | |
| } | |
| } // namespace | |
| CXFA_LocaleValue XFA_GetLocaleValue(const CXFA_Node* pNode) { | |
| const auto* 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(); | |
| return CXFA_LocaleValue(XFA_GetLocaleValueType(pValueChild->GetElementType()), | |
| pNode->GetRawValue(), | |
| pNode->GetDocument()->GetLocaleMgr()); | |
| } | |
| CXFA_LocaleValue::ValueType XFA_GetLocaleValueType(XFA_Element element) { | |
| switch (element) { | |
| case XFA_Element::Decimal: | |
| return CXFA_LocaleValue::ValueType::kDecimal; | |
| case XFA_Element::Float: | |
| return CXFA_LocaleValue::ValueType::kFloat; | |
| case XFA_Element::Date: | |
| return CXFA_LocaleValue::ValueType::kDate; | |
| case XFA_Element::Time: | |
| return CXFA_LocaleValue::ValueType::kTime; | |
| case XFA_Element::DateTime: | |
| return CXFA_LocaleValue::ValueType::kDateTime; | |
| case XFA_Element::Boolean: | |
| return CXFA_LocaleValue::ValueType::kBoolean; | |
| case XFA_Element::Integer: | |
| return CXFA_LocaleValue::ValueType::kInteger; | |
| case XFA_Element::Text: | |
| return CXFA_LocaleValue::ValueType::kText; | |
| default: | |
| return CXFA_LocaleValue::ValueType::kNull; | |
| } | |
| } | |
| 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) { | |
| 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(const CXFA_Node* pFieldNode) { | |
| if (!pFieldNode) | |
| return false; | |
| const auto* 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; | |
| } |