cxfa_dataexporter.cpp revision 5ae9d0c6fd838a2967cca72aa5751b51dadc2769
1// Copyright 2014 PDFium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com 6 7#include "xfa/fxfa/parser/cxfa_dataexporter.h" 8 9#include <vector> 10 11#include "core/fxcrt/fx_basic.h" 12#include "third_party/base/stl_util.h" 13#include "xfa/fde/xml/fde_xml_imp.h" 14#include "xfa/fgas/crt/fgas_codepage.h" 15#include "xfa/fxfa/parser/cxfa_document.h" 16#include "xfa/fxfa/parser/cxfa_widgetdata.h" 17#include "xfa/fxfa/parser/xfa_object.h" 18 19namespace { 20 21CFX_WideString ExportEncodeAttribute(const CFX_WideString& str) { 22 CFX_WideTextBuf textBuf; 23 int32_t iLen = str.GetLength(); 24 for (int32_t i = 0; i < iLen; i++) { 25 switch (str[i]) { 26 case '&': 27 textBuf << L"&"; 28 break; 29 case '<': 30 textBuf << L"<"; 31 break; 32 case '>': 33 textBuf << L">"; 34 break; 35 case '\'': 36 textBuf << L"'"; 37 break; 38 case '\"': 39 textBuf << L"""; 40 break; 41 default: 42 textBuf.AppendChar(str[i]); 43 } 44 } 45 return textBuf.MakeString(); 46} 47 48CFX_WideString ExportEncodeContent(const CFX_WideStringC& str) { 49 CFX_WideTextBuf textBuf; 50 int32_t iLen = str.GetLength(); 51 for (int32_t i = 0; i < iLen; i++) { 52 FX_WCHAR ch = str.GetAt(i); 53 if (!FDE_IsXMLValidChar(ch)) 54 continue; 55 56 if (ch == '&') { 57 textBuf << L"&"; 58 } else if (ch == '<') { 59 textBuf << L"<"; 60 } else if (ch == '>') { 61 textBuf << L">"; 62 } else if (ch == '\'') { 63 textBuf << L"'"; 64 } else if (ch == '\"') { 65 textBuf << L"""; 66 } else if (ch == ' ') { 67 if (i && str.GetAt(i - 1) != ' ') { 68 textBuf.AppendChar(' '); 69 } else { 70 textBuf << L" "; 71 } 72 } else { 73 textBuf.AppendChar(str.GetAt(i)); 74 } 75 } 76 return textBuf.MakeString(); 77} 78 79void SaveAttribute(CXFA_Node* pNode, 80 XFA_ATTRIBUTE eName, 81 const CFX_WideStringC& wsName, 82 bool bProto, 83 CFX_WideString& wsOutput) { 84 CFX_WideString wsValue; 85 if ((!bProto && !pNode->HasAttribute((XFA_ATTRIBUTE)eName, bProto)) || 86 !pNode->GetAttribute((XFA_ATTRIBUTE)eName, wsValue, false)) { 87 return; 88 } 89 wsValue = ExportEncodeAttribute(wsValue); 90 wsOutput += L" "; 91 wsOutput += wsName; 92 wsOutput += L"=\""; 93 wsOutput += wsValue; 94 wsOutput += L"\""; 95} 96 97bool AttributeSaveInDataModel(CXFA_Node* pNode, XFA_ATTRIBUTE eAttribute) { 98 bool bSaveInDataModel = false; 99 if (pNode->GetElementType() != XFA_Element::Image) 100 return bSaveInDataModel; 101 102 CXFA_Node* pValueNode = pNode->GetNodeItem(XFA_NODEITEM_Parent); 103 if (!pValueNode || pValueNode->GetElementType() != XFA_Element::Value) 104 return bSaveInDataModel; 105 106 CXFA_Node* pFieldNode = pValueNode->GetNodeItem(XFA_NODEITEM_Parent); 107 if (pFieldNode && pFieldNode->GetBindData() && 108 eAttribute == XFA_ATTRIBUTE_Href) { 109 bSaveInDataModel = true; 110 } 111 return bSaveInDataModel; 112} 113 114bool ContentNodeNeedtoExport(CXFA_Node* pContentNode) { 115 CFX_WideString wsContent; 116 if (!pContentNode->TryContent(wsContent, false, false)) 117 return false; 118 119 ASSERT(pContentNode->IsContentNode()); 120 CXFA_Node* pParentNode = pContentNode->GetNodeItem(XFA_NODEITEM_Parent); 121 if (!pParentNode || pParentNode->GetElementType() != XFA_Element::Value) 122 return true; 123 124 CXFA_Node* pGrandParentNode = pParentNode->GetNodeItem(XFA_NODEITEM_Parent); 125 if (!pGrandParentNode || !pGrandParentNode->IsContainerNode()) 126 return true; 127 if (pGrandParentNode->GetBindData()) 128 return false; 129 130 CXFA_WidgetData* pWidgetData = pGrandParentNode->GetWidgetData(); 131 XFA_Element eUIType = pWidgetData->GetUIType(); 132 if (eUIType == XFA_Element::PasswordEdit) 133 return false; 134 return true; 135} 136 137void RecognizeXFAVersionNumber(CXFA_Node* pTemplateRoot, 138 CFX_WideString& wsVersionNumber) { 139 wsVersionNumber.clear(); 140 if (!pTemplateRoot) 141 return; 142 143 CFX_WideString wsTemplateNS; 144 if (!pTemplateRoot->TryNamespace(wsTemplateNS)) 145 return; 146 147 XFA_VERSION eVersion = 148 pTemplateRoot->GetDocument()->RecognizeXFAVersionNumber(wsTemplateNS); 149 if (eVersion == XFA_VERSION_UNKNOWN) 150 eVersion = XFA_VERSION_DEFAULT; 151 152 wsVersionNumber.Format(L"%i.%i", eVersion / 100, eVersion % 100); 153} 154 155void RegenerateFormFile_Changed(CXFA_Node* pNode, 156 CFX_WideTextBuf& buf, 157 bool bSaveXML) { 158 CFX_WideString wsAttrs; 159 int32_t iAttrs = 0; 160 const uint8_t* pAttrs = 161 XFA_GetElementAttributes(pNode->GetElementType(), iAttrs); 162 while (iAttrs--) { 163 const XFA_ATTRIBUTEINFO* pAttr = 164 XFA_GetAttributeByID((XFA_ATTRIBUTE)pAttrs[iAttrs]); 165 if (pAttr->eName == XFA_ATTRIBUTE_Name || 166 (AttributeSaveInDataModel(pNode, pAttr->eName) && !bSaveXML)) { 167 continue; 168 } 169 CFX_WideString wsAttr; 170 SaveAttribute(pNode, pAttr->eName, pAttr->pName, bSaveXML, wsAttr); 171 wsAttrs += wsAttr; 172 } 173 174 CFX_WideString wsChildren; 175 switch (pNode->GetObjectType()) { 176 case XFA_ObjectType::ContentNode: { 177 if (!bSaveXML && !ContentNodeNeedtoExport(pNode)) 178 break; 179 180 CXFA_Node* pRawValueNode = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 181 while (pRawValueNode && 182 pRawValueNode->GetElementType() != XFA_Element::SharpxHTML && 183 pRawValueNode->GetElementType() != XFA_Element::Sharptext && 184 pRawValueNode->GetElementType() != XFA_Element::Sharpxml) { 185 pRawValueNode = pRawValueNode->GetNodeItem(XFA_NODEITEM_NextSibling); 186 } 187 if (!pRawValueNode) 188 break; 189 190 CFX_WideString wsContentType; 191 pNode->GetAttribute(XFA_ATTRIBUTE_ContentType, wsContentType, false); 192 if (pRawValueNode->GetElementType() == XFA_Element::SharpxHTML && 193 wsContentType == L"text/html") { 194 CFDE_XMLNode* pExDataXML = pNode->GetXMLMappingNode(); 195 if (!pExDataXML) 196 break; 197 198 CFDE_XMLNode* pRichTextXML = 199 pExDataXML->GetNodeItem(CFDE_XMLNode::FirstChild); 200 if (!pRichTextXML) 201 break; 202 203 CFX_RetainPtr<IFX_MemoryStream> pMemStream = 204 IFX_MemoryStream::Create(true); 205 206 // Note: ambiguous without cast below. 207 CFX_RetainPtr<IFGAS_Stream> pTempStream = IFGAS_Stream::CreateStream( 208 CFX_RetainPtr<IFX_SeekableWriteStream>(pMemStream), 209 FX_STREAMACCESS_Text | FX_STREAMACCESS_Write | 210 FX_STREAMACCESS_Append); 211 212 pTempStream->SetCodePage(FX_CODEPAGE_UTF8); 213 pRichTextXML->SaveXMLNode(pTempStream); 214 wsChildren += CFX_WideString::FromUTF8( 215 CFX_ByteStringC(pMemStream->GetBuffer(), pMemStream->GetSize())); 216 } else if (pRawValueNode->GetElementType() == XFA_Element::Sharpxml && 217 wsContentType == L"text/xml") { 218 CFX_WideString wsRawValue; 219 pRawValueNode->GetAttribute(XFA_ATTRIBUTE_Value, wsRawValue, false); 220 if (wsRawValue.IsEmpty()) 221 break; 222 223 std::vector<CFX_WideString> wsSelTextArray; 224 int32_t iStart = 0; 225 int32_t iEnd = wsRawValue.Find(L'\n', iStart); 226 iEnd = (iEnd == -1) ? wsRawValue.GetLength() : iEnd; 227 while (iEnd >= iStart) { 228 wsSelTextArray.push_back(wsRawValue.Mid(iStart, iEnd - iStart)); 229 iStart = iEnd + 1; 230 if (iStart >= wsRawValue.GetLength()) 231 break; 232 233 iEnd = wsRawValue.Find(L'\n', iStart); 234 } 235 CXFA_Node* pParentNode = pNode->GetNodeItem(XFA_NODEITEM_Parent); 236 ASSERT(pParentNode); 237 CXFA_Node* pGrandparentNode = 238 pParentNode->GetNodeItem(XFA_NODEITEM_Parent); 239 ASSERT(pGrandparentNode); 240 CFX_WideString bodyTagName; 241 bodyTagName = pGrandparentNode->GetCData(XFA_ATTRIBUTE_Name); 242 if (bodyTagName.IsEmpty()) 243 bodyTagName = L"ListBox1"; 244 245 buf << L"<"; 246 buf << bodyTagName; 247 buf << L" xmlns=\"\"\n>"; 248 for (int32_t i = 0; i < pdfium::CollectionSize<int32_t>(wsSelTextArray); 249 i++) { 250 buf << L"<value\n>"; 251 buf << ExportEncodeContent(wsSelTextArray[i].AsStringC()); 252 buf << L"</value\n>"; 253 } 254 buf << L"</"; 255 buf << bodyTagName; 256 buf << L"\n>"; 257 wsChildren += buf.AsStringC(); 258 buf.Clear(); 259 } else { 260 CFX_WideStringC wsValue = pRawValueNode->GetCData(XFA_ATTRIBUTE_Value); 261 wsChildren += ExportEncodeContent(wsValue); 262 } 263 break; 264 } 265 case XFA_ObjectType::TextNode: 266 case XFA_ObjectType::NodeC: 267 case XFA_ObjectType::NodeV: { 268 CFX_WideStringC wsValue = pNode->GetCData(XFA_ATTRIBUTE_Value); 269 wsChildren += ExportEncodeContent(wsValue); 270 break; 271 } 272 default: 273 if (pNode->GetElementType() == XFA_Element::Items) { 274 CXFA_Node* pTemplateNode = pNode->GetTemplateNode(); 275 if (!pTemplateNode || 276 pTemplateNode->CountChildren(XFA_Element::Unknown) != 277 pNode->CountChildren(XFA_Element::Unknown)) { 278 bSaveXML = true; 279 } 280 } 281 CFX_WideTextBuf newBuf; 282 CXFA_Node* pChildNode = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 283 while (pChildNode) { 284 RegenerateFormFile_Changed(pChildNode, newBuf, bSaveXML); 285 wsChildren += newBuf.AsStringC(); 286 newBuf.Clear(); 287 pChildNode = pChildNode->GetNodeItem(XFA_NODEITEM_NextSibling); 288 } 289 if (!bSaveXML && !wsChildren.IsEmpty() && 290 pNode->GetElementType() == XFA_Element::Items) { 291 wsChildren.clear(); 292 bSaveXML = true; 293 CXFA_Node* pChild = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 294 while (pChild) { 295 RegenerateFormFile_Changed(pChild, newBuf, bSaveXML); 296 wsChildren += newBuf.AsStringC(); 297 newBuf.Clear(); 298 pChild = pChild->GetNodeItem(XFA_NODEITEM_NextSibling); 299 } 300 } 301 break; 302 } 303 304 if (!wsChildren.IsEmpty() || !wsAttrs.IsEmpty() || 305 pNode->HasAttribute(XFA_ATTRIBUTE_Name)) { 306 CFX_WideStringC wsElement = pNode->GetClassName(); 307 CFX_WideString wsName; 308 SaveAttribute(pNode, XFA_ATTRIBUTE_Name, L"name", true, wsName); 309 buf << L"<"; 310 buf << wsElement; 311 buf << wsName; 312 buf << wsAttrs; 313 if (wsChildren.IsEmpty()) { 314 buf << L"\n/>"; 315 } else { 316 buf << L"\n>"; 317 buf << wsChildren; 318 buf << L"</"; 319 buf << wsElement; 320 buf << L"\n>"; 321 } 322 } 323} 324 325void RegenerateFormFile_Container(CXFA_Node* pNode, 326 const CFX_RetainPtr<IFGAS_Stream>& pStream, 327 bool bSaveXML = false) { 328 XFA_Element eType = pNode->GetElementType(); 329 if (eType == XFA_Element::Field || eType == XFA_Element::Draw || 330 !pNode->IsContainerNode()) { 331 CFX_WideTextBuf buf; 332 RegenerateFormFile_Changed(pNode, buf, bSaveXML); 333 FX_STRSIZE nLen = buf.GetLength(); 334 if (nLen > 0) 335 pStream->WriteString((const FX_WCHAR*)buf.GetBuffer(), nLen); 336 return; 337 } 338 339 CFX_WideStringC wsElement = pNode->GetClassName(); 340 pStream->WriteString(L"<", 1); 341 pStream->WriteString(wsElement.c_str(), wsElement.GetLength()); 342 CFX_WideString wsOutput; 343 SaveAttribute(pNode, XFA_ATTRIBUTE_Name, L"name", true, wsOutput); 344 CFX_WideString wsAttrs; 345 int32_t iAttrs = 0; 346 const uint8_t* pAttrs = 347 XFA_GetElementAttributes(pNode->GetElementType(), iAttrs); 348 while (iAttrs--) { 349 const XFA_ATTRIBUTEINFO* pAttr = 350 XFA_GetAttributeByID((XFA_ATTRIBUTE)pAttrs[iAttrs]); 351 if (pAttr->eName == XFA_ATTRIBUTE_Name) 352 continue; 353 354 CFX_WideString wsAttr; 355 SaveAttribute(pNode, pAttr->eName, pAttr->pName, false, wsAttr); 356 wsOutput += wsAttr; 357 } 358 359 if (!wsOutput.IsEmpty()) 360 pStream->WriteString(wsOutput.c_str(), wsOutput.GetLength()); 361 362 CXFA_Node* pChildNode = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 363 if (pChildNode) { 364 pStream->WriteString(L"\n>", 2); 365 while (pChildNode) { 366 RegenerateFormFile_Container(pChildNode, pStream, bSaveXML); 367 pChildNode = pChildNode->GetNodeItem(XFA_NODEITEM_NextSibling); 368 } 369 pStream->WriteString(L"</", 2); 370 pStream->WriteString(wsElement.c_str(), wsElement.GetLength()); 371 pStream->WriteString(L"\n>", 2); 372 } else { 373 pStream->WriteString(L"\n/>", 3); 374 } 375} 376 377} // namespace 378 379void XFA_DataExporter_RegenerateFormFile( 380 CXFA_Node* pNode, 381 const CFX_RetainPtr<IFGAS_Stream>& pStream, 382 const FX_CHAR* pChecksum, 383 bool bSaveXML) { 384 if (pNode->IsModelNode()) { 385 static const FX_WCHAR s_pwsTagName[] = L"<form"; 386 static const FX_WCHAR s_pwsClose[] = L"</form\n>"; 387 pStream->WriteString(s_pwsTagName, FXSYS_wcslen(s_pwsTagName)); 388 if (pChecksum) { 389 static const FX_WCHAR s_pwChecksum[] = L" checksum=\""; 390 CFX_WideString wsChecksum = CFX_WideString::FromUTF8(pChecksum); 391 pStream->WriteString(s_pwChecksum, FXSYS_wcslen(s_pwChecksum)); 392 pStream->WriteString(wsChecksum.c_str(), wsChecksum.GetLength()); 393 pStream->WriteString(L"\"", 1); 394 } 395 pStream->WriteString(L" xmlns=\"", FXSYS_wcslen(L" xmlns=\"")); 396 const FX_WCHAR* pURI = XFA_GetPacketByIndex(XFA_PACKET_Form)->pURI; 397 pStream->WriteString(pURI, FXSYS_wcslen(pURI)); 398 CFX_WideString wsVersionNumber; 399 RecognizeXFAVersionNumber( 400 ToNode(pNode->GetDocument()->GetXFAObject(XFA_HASHCODE_Template)), 401 wsVersionNumber); 402 if (wsVersionNumber.IsEmpty()) 403 wsVersionNumber = L"2.8"; 404 405 wsVersionNumber += L"/\"\n>"; 406 pStream->WriteString(wsVersionNumber.c_str(), wsVersionNumber.GetLength()); 407 CXFA_Node* pChildNode = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 408 while (pChildNode) { 409 RegenerateFormFile_Container(pChildNode, pStream); 410 pChildNode = pChildNode->GetNodeItem(XFA_NODEITEM_NextSibling); 411 } 412 pStream->WriteString(s_pwsClose, FXSYS_wcslen(s_pwsClose)); 413 } else { 414 RegenerateFormFile_Container(pNode, pStream, bSaveXML); 415 } 416} 417 418void XFA_DataExporter_DealWithDataGroupNode(CXFA_Node* pDataNode) { 419 if (!pDataNode || pDataNode->GetElementType() == XFA_Element::DataValue) 420 return; 421 422 int32_t iChildNum = 0; 423 for (CXFA_Node* pChildNode = pDataNode->GetNodeItem(XFA_NODEITEM_FirstChild); 424 pChildNode; 425 pChildNode = pChildNode->GetNodeItem(XFA_NODEITEM_NextSibling)) { 426 iChildNum++; 427 XFA_DataExporter_DealWithDataGroupNode(pChildNode); 428 } 429 430 if (pDataNode->GetElementType() != XFA_Element::DataGroup) 431 return; 432 433 if (iChildNum > 0) { 434 CFDE_XMLNode* pXMLNode = pDataNode->GetXMLMappingNode(); 435 ASSERT(pXMLNode->GetType() == FDE_XMLNODE_Element); 436 CFDE_XMLElement* pXMLElement = static_cast<CFDE_XMLElement*>(pXMLNode); 437 if (pXMLElement->HasAttribute(L"xfa:dataNode")) 438 pXMLElement->RemoveAttribute(L"xfa:dataNode"); 439 440 return; 441 } 442 443 CFDE_XMLNode* pXMLNode = pDataNode->GetXMLMappingNode(); 444 ASSERT(pXMLNode->GetType() == FDE_XMLNODE_Element); 445 static_cast<CFDE_XMLElement*>(pXMLNode)->SetString(L"xfa:dataNode", 446 L"dataGroup"); 447} 448 449CXFA_DataExporter::CXFA_DataExporter(CXFA_Document* pDocument) 450 : m_pDocument(pDocument) { 451 ASSERT(m_pDocument); 452} 453 454bool CXFA_DataExporter::Export( 455 const CFX_RetainPtr<IFX_SeekableWriteStream>& pWrite) { 456 return Export(pWrite, m_pDocument->GetRoot(), 0, nullptr); 457} 458 459bool CXFA_DataExporter::Export( 460 const CFX_RetainPtr<IFX_SeekableWriteStream>& pWrite, 461 CXFA_Node* pNode, 462 uint32_t dwFlag, 463 const FX_CHAR* pChecksum) { 464 ASSERT(pWrite); 465 if (!pWrite) 466 return false; 467 468 CFX_RetainPtr<IFGAS_Stream> pStream = IFGAS_Stream::CreateStream( 469 pWrite, 470 FX_STREAMACCESS_Text | FX_STREAMACCESS_Write | FX_STREAMACCESS_Append); 471 if (!pStream) 472 return false; 473 474 pStream->SetCodePage(FX_CODEPAGE_UTF8); 475 return Export(pStream, pNode, dwFlag, pChecksum); 476} 477 478bool CXFA_DataExporter::Export(const CFX_RetainPtr<IFGAS_Stream>& pStream, 479 CXFA_Node* pNode, 480 uint32_t dwFlag, 481 const FX_CHAR* pChecksum) { 482 CFDE_XMLDoc* pXMLDoc = m_pDocument->GetXMLDoc(); 483 if (pNode->IsModelNode()) { 484 switch (pNode->GetPacketID()) { 485 case XFA_XDPPACKET_XDP: { 486 static const FX_WCHAR s_pwsPreamble[] = 487 L"<xdp:xdp xmlns:xdp=\"http://ns.adobe.com/xdp/\">"; 488 pStream->WriteString(s_pwsPreamble, FXSYS_wcslen(s_pwsPreamble)); 489 for (CXFA_Node* pChild = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 490 pChild; pChild = pChild->GetNodeItem(XFA_NODEITEM_NextSibling)) { 491 Export(pStream, pChild, dwFlag, pChecksum); 492 } 493 static const FX_WCHAR s_pwsPostamble[] = L"</xdp:xdp\n>"; 494 pStream->WriteString(s_pwsPostamble, FXSYS_wcslen(s_pwsPostamble)); 495 break; 496 } 497 case XFA_XDPPACKET_Datasets: { 498 CFDE_XMLElement* pElement = 499 static_cast<CFDE_XMLElement*>(pNode->GetXMLMappingNode()); 500 if (!pElement || pElement->GetType() != FDE_XMLNODE_Element) 501 return false; 502 503 CXFA_Node* pDataNode = pNode->GetNodeItem(XFA_NODEITEM_FirstChild); 504 ASSERT(pDataNode); 505 XFA_DataExporter_DealWithDataGroupNode(pDataNode); 506 pXMLDoc->SaveXMLNode(pStream, pElement); 507 break; 508 } 509 case XFA_XDPPACKET_Form: { 510 XFA_DataExporter_RegenerateFormFile(pNode, pStream, pChecksum); 511 break; 512 } 513 case XFA_XDPPACKET_Template: 514 default: { 515 CFDE_XMLElement* pElement = 516 static_cast<CFDE_XMLElement*>(pNode->GetXMLMappingNode()); 517 if (!pElement || pElement->GetType() != FDE_XMLNODE_Element) 518 return false; 519 520 pXMLDoc->SaveXMLNode(pStream, pElement); 521 break; 522 } 523 } 524 return true; 525 } 526 527 CXFA_Node* pDataNode = pNode->GetNodeItem(XFA_NODEITEM_Parent); 528 CXFA_Node* pExportNode = pNode; 529 for (CXFA_Node* pChildNode = pDataNode->GetNodeItem(XFA_NODEITEM_FirstChild); 530 pChildNode; 531 pChildNode = pChildNode->GetNodeItem(XFA_NODEITEM_NextSibling)) { 532 if (pChildNode != pNode) { 533 pExportNode = pDataNode; 534 break; 535 } 536 } 537 CFDE_XMLElement* pElement = 538 static_cast<CFDE_XMLElement*>(pExportNode->GetXMLMappingNode()); 539 if (!pElement || pElement->GetType() != FDE_XMLNODE_Element) 540 return false; 541 542 XFA_DataExporter_DealWithDataGroupNode(pExportNode); 543 pElement->SetString(L"xmlns:xfa", L"http://www.xfa.org/schema/xfa-data/1.0/"); 544 pXMLDoc->SaveXMLNode(pStream, pElement); 545 pElement->RemoveAttribute(L"xmlns:xfa"); 546 547 return true; 548} 549