tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / sw / source / filter / ww8 / ww8par5.cxx
blob8c18d83b339305f82ef4e7b71ddc488a44c03b84
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_features.h>
21 #include <config_fuzzers.h>
23 #include <sal/types.h>
24 #include <tools/solar.h>
25 #include <comphelper/processfactory.hxx>
26 #include <comphelper/string.hxx>
27 #include <comphelper/simplefileaccessinteraction.hxx>
28 #include <com/sun/star/embed/XStorage.hpp>
29 #include <com/sun/star/embed/ElementModes.hpp>
30 #include <com/sun/star/embed/XTransactedObject.hpp>
31 #include <com/sun/star/task/InteractionHandler.hpp>
33 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
34 #include <svl/cintitem.hxx>
35 #include <svl/lngmisc.hxx>
36 #include <svl/urihelper.hxx>
37 #include <svl/numformat.hxx>
38 #include <svl/zforlist.hxx>
39 #include <svl/zformat.hxx>
40 #include <sfx2/linkmgr.hxx>
41 #include <rtl/character.hxx>
42 #include <unotools/charclass.hxx>
44 #include <ucbhelper/content.hxx>
45 #include <ucbhelper/commandenvironment.hxx>
47 #include <com/sun/star/i18n/XBreakIterator.hpp>
48 #include <hintids.hxx>
49 #include <editeng/fontitem.hxx>
50 #include <editeng/fhgtitem.hxx>
51 #include <editeng/langitem.hxx>
52 #include <fmtfld.hxx>
53 #include <fmtanchr.hxx>
54 #include <pam.hxx>
55 #include <doc.hxx>
56 #include <IDocumentFieldsAccess.hxx>
57 #include <IDocumentMarkAccess.hxx>
58 #include <IDocumentState.hxx>
59 #include <flddat.hxx>
60 #include <docufld.hxx>
61 #include <usrfld.hxx>
62 #include <reffld.hxx>
63 #include <IMark.hxx>
64 #include <expfld.hxx>
65 #include <dbfld.hxx>
66 #include <tox.hxx>
67 #include <section.hxx>
68 #include <ndtxt.hxx>
69 #include <fmtinfmt.hxx>
70 #include <chpfld.hxx>
71 #include <fmtruby.hxx>
72 #include <charfmt.hxx>
73 #include <breakit.hxx>
74 #include <fmtclds.hxx>
75 #include <poolfmt.hxx>
76 #include <SwStyleNameMapper.hxx>
78 #include "ww8scan.hxx"
79 #include "ww8par.hxx"
80 #include "writerhelper.hxx"
81 #include <o3tl/safeint.hxx>
82 #include <o3tl/string_view.hxx>
83 #include <xmloff/odffields.hxx>
84 #include <osl/diagnose.h>
85 #include <officecfg/Office/Common.hxx>
87 #include <algorithm>
88 #include <string_view>
90 #define MAX_FIELDLEN 64000
92 #define WW8_TOX_LEVEL_DELIM ':'
94 using namespace ::com::sun::star;
95 using namespace msfilter::util;
96 using namespace sw::util;
97 using namespace sw::mark;
98 using namespace nsSwDocInfoSubType;
100 // Bookmarks
101 namespace
103 // #120879# - helper method to identify a bookmark name to match the internal TOC bookmark naming convention
104 bool IsTOCBookmarkName(std::u16string_view rName)
106 return o3tl::starts_with(rName, u"_Toc") || o3tl::starts_with(rName, Concat2View(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()+"_Toc"));
109 OUString EnsureTOCBookmarkName(const OUString& rName)
111 OUString sTmp = rName;
112 if ( IsTOCBookmarkName ( rName ) )
114 if ( ! rName.startsWith(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()) )
115 sTmp = IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix() + rName;
117 return sTmp;
121 tools::Long SwWW8ImplReader::Read_Book(WW8PLCFManResult*)
123 // should also work via pRes.nCo2OrIdx
124 WW8PLCFx_Book* pB = m_xPlcxMan->GetBook();
125 if( !pB )
127 OSL_ENSURE( pB, "WW8PLCFx_Book - Pointer does not exist" );
128 return 0;
131 eBookStatus eB = pB->GetStatus();
132 if (eB & BOOK_IGNORE)
133 return 0; // ignore bookmark
135 if (pB->GetIsEnd())
137 m_xReffedStck->SetAttr(*m_pPaM->GetPoint(), RES_FLTR_BOOKMARK, true,
138 pB->GetHandle(), (eB & BOOK_FIELD)!=0);
139 return 0;
142 // "_Hlt*" are unnecessary
143 const OUString* pName = pB->GetName();
144 // Now, as we read the TOC field completely, we also need the hyperlinks inside keep available.
145 // So the hidden bookmarks inside for hyperlink jumping also should be kept.
146 if ( !pName ||
147 pName->startsWithIgnoreAsciiCase( "_Hlt" ) )
149 return 0;
152 // do NOT call ToUpper as the bookmark name can also be a hyperlink target!
154 OUString aVal;
155 if( SwFltGetFlag( m_nFieldFlags, SwFltControlStack::BOOK_TO_VAR_REF ) )
157 // set variable for translation bookmark
158 tools::Long nLen = pB->GetLen();
159 if( nLen > MAX_FIELDLEN )
160 nLen = MAX_FIELDLEN;
162 sal_uInt64 nOldPos = m_pStrm->Tell();
163 m_xSBase->WW8ReadString( *m_pStrm, aVal, pB->GetStartPos(), nLen,
164 m_eStructCharSet );
165 m_pStrm->Seek( nOldPos );
167 // now here the implementation of the old "QuoteString" and
168 // I hope with a better performance as before. It's also only
169 // needed if the filterflags say we will convert bookmarks
170 // to SetExpFields! And this the exception!
172 bool bSetAsHex;
173 bool bAllowCr = SwFltGetFlag(m_nFieldFlags,
174 SwFltControlStack::ALLOW_FLD_CR);
176 for( sal_Int32 nI = 0;
177 nI < aVal.getLength() && aVal.getLength() < (MAX_FIELDLEN - 4);
178 ++nI )
180 const sal_Unicode cChar = aVal[nI];
181 switch( cChar )
183 case 0x0b:
184 case 0x0c:
185 case 0x0d:
186 if( bAllowCr )
188 aVal = aVal.replaceAt( nI, 1, u"\n" );
189 bSetAsHex = false;
191 else
192 bSetAsHex = true;
193 break;
195 case 0xFE:
196 case 0xFF:
197 bSetAsHex = true;
198 break;
200 default:
201 bSetAsHex = 0x20 > cChar;
202 break;
205 if( bSetAsHex )
207 //all Hex-Numbers with \x before
208 OUString sTmp( u"\\x"_ustr );
209 if( cChar < 0x10 )
210 sTmp += "0";
211 sTmp += OUString::number( cChar, 16 );
212 aVal = aVal.replaceAt( nI, 1 , sTmp );
213 nI += sTmp.getLength() - 1;
217 if ( aVal.getLength() > (MAX_FIELDLEN - 4))
218 aVal = aVal.copy( 0, MAX_FIELDLEN - 4 );
221 //e.g. inserting bookmark around field result, so we need to put
222 //it around the entire writer field, as we don't have the separation
223 //of field and field result of word, see #i16941#
224 SwPosition aStart(*m_pPaM->GetPoint());
225 if (!m_aFieldStack.empty())
227 const WW8FieldEntry &rTest = m_aFieldStack.back();
228 aStart = rTest.maStartPos;
231 const OUString sOrigName = BookmarkToWriter(*pName);
232 m_xReffedStck->NewAttr( aStart,
233 SwFltBookmark( EnsureTOCBookmarkName( sOrigName ), aVal, pB->GetHandle(), IsTOCBookmarkName( sOrigName ) ));
234 return 0;
237 tools::Long SwWW8ImplReader::Read_AtnBook(WW8PLCFManResult*)
239 if (WW8PLCFx_AtnBook* pAtnBook = m_xPlcxMan->GetAtnBook())
241 if (pAtnBook->getIsEnd())
242 m_xReffedStck->SetAttr(*m_pPaM->GetPoint(), RES_FLTR_ANNOTATIONMARK, true, pAtnBook->getHandle());
243 else
244 m_xReffedStck->NewAttr(*m_pPaM->GetPoint(), CntUInt16Item(RES_FLTR_ANNOTATIONMARK, pAtnBook->getHandle()));
246 return 0;
249 tools::Long SwWW8ImplReader::Read_FactoidBook(WW8PLCFManResult*)
251 if (WW8PLCFx_FactoidBook* pFactoidBook = m_xPlcxMan->GetFactoidBook())
253 if (pFactoidBook->getIsEnd())
254 m_xReffedStck->SetAttr(*m_pPaM->GetPoint(), RES_FLTR_RDFMARK, true, pFactoidBook->getHandle());
255 else
257 SwFltRDFMark aMark;
258 aMark.SetHandle(pFactoidBook->getHandle());
259 GetSmartTagInfo(aMark);
260 m_xReffedStck->NewAttr(*m_pPaM->GetPoint(), aMark);
263 return 0;
266 // general help methods to separate parameters
268 /// translate FieldParameter names into the system character set and
269 /// at the same time, double backslashes are converted into single ones
270 OUString SwWW8ImplReader::ConvertFFileName(const OUString& rOrg)
272 OUString aName = rOrg.replaceAll("\\\\", "\\");
273 aName = aName.replaceAll("%20", " ");
275 // remove attached quotation marks
276 if (aName.endsWith("\""))
277 aName = aName.copy(0, aName.getLength()-1);
279 // Need the more sophisticated url converter.
280 if (!aName.isEmpty())
281 aName = URIHelper::SmartRel2Abs(
282 INetURLObject(m_sBaseURL), aName, Link<OUString *, bool>(), false);
284 return aName;
287 namespace
289 /// translate FieldParameter names into the
290 /// system character set and makes them uppercase
291 void ConvertUFName( OUString& rName )
293 rName = GetAppCharClass().uppercase( rName );
297 static void lcl_ConvertSequenceName(OUString& rSequenceName)
299 ConvertUFName(rSequenceName);
300 if ('0' <= rSequenceName[0] && '9' >= rSequenceName[0])
301 rSequenceName = "_" + rSequenceName;
304 // FindParaStart() finds 1st Parameter that follows '\' and cToken
305 // and returns start of this parameter or -1
306 static sal_Int32 FindParaStart( std::u16string_view aStr, sal_Unicode cToken, sal_Unicode cToken2 )
308 bool bStr = false; // ignore inside a string
310 for( size_t nBuf = 0; nBuf+1 < aStr.size(); nBuf++ )
312 if( aStr[ nBuf ] == '"' )
313 bStr = !bStr;
315 if( !bStr
316 && aStr[ nBuf ] == '\\'
317 && ( aStr[ nBuf + 1 ] == cToken
318 || aStr[ nBuf + 1 ] == cToken2 ) )
320 nBuf += 2;
321 // skip spaces between cToken and its parameters
322 while( nBuf < aStr.size()
323 && aStr[ nBuf ] == ' ' )
324 nBuf++;
325 // return start of parameters
326 return nBuf < aStr.size() ? nBuf : -1;
329 return -1;
332 // FindPara() finds the first parameter including '\' and cToken.
333 // A new String will be allocated (has to be deallocated by the caller)
334 // and everything that is part of the parameter will be returned.
335 static OUString FindPara( std::u16string_view aStr, sal_Unicode cToken, sal_Unicode cToken2 )
337 sal_Int32 n2; // end
338 sal_Int32 n = FindParaStart( aStr, cToken, cToken2 ); // start
339 if( n == -1)
340 return OUString();
342 if( aStr[ n ] == '"'
343 || aStr[ n ] == 132 )
344 { // Quotationmark in front of parameter
345 n++; // Skip quotationmark
346 n2 = n; // search for the end starting from here
347 while( n2 < sal_Int32(aStr.size())
348 && aStr[ n2 ] != 147
349 && aStr[ n2 ] != '"' )
350 n2++; // search end of parameter
352 else
353 { // no quotationmarks
354 n2 = n; // search for the end starting from here
355 while( n2 < sal_Int32(aStr.size())
356 && aStr[ n2 ] != ' ' )
357 n2++; // search end of parameter
359 return OUString(aStr.substr( n, n2-n ));
362 static SvxNumType GetNumTypeFromName(const OUString& rStr,
363 bool bAllowPageDesc = false)
365 SvxNumType eTyp = bAllowPageDesc ? SVX_NUM_PAGEDESC : SVX_NUM_ARABIC;
366 if (rStr.isEmpty())
367 return eTyp;
369 if( rStr.startsWithIgnoreAsciiCase( "Arabi" ) ) // Arabisch, Arabic
370 eTyp = SVX_NUM_ARABIC;
371 else if( rStr.startsWith( "misch" ) ) // r"omisch
372 eTyp = SVX_NUM_ROMAN_LOWER;
373 else if( rStr.startsWith( "MISCH" ) ) // R"OMISCH
374 eTyp = SVX_NUM_ROMAN_UPPER;
375 else if( rStr.startsWithIgnoreAsciiCase( "alphabeti" ) )// alphabetisch, alphabetic
376 eTyp = ( rStr[0] == 'A' )
377 ? SVX_NUM_CHARS_UPPER_LETTER_N
378 : SVX_NUM_CHARS_LOWER_LETTER_N;
379 else if( rStr.startsWithIgnoreAsciiCase( "roman" ) ) // us
380 eTyp = ( rStr[0] == 'R' )
381 ? SVX_NUM_ROMAN_UPPER
382 : SVX_NUM_ROMAN_LOWER;
383 return eTyp;
386 static SvxNumType GetNumberPara(std::u16string_view aStr, bool bAllowPageDesc = false)
388 OUString s( FindPara( aStr, '*', '*' ) ); // Type of number
389 SvxNumType aType = GetNumTypeFromName( s, bAllowPageDesc );
390 return aType;
393 bool SwWW8ImplReader::ForceFieldLanguage(SwField &rField, LanguageType nLang)
395 bool bRet(false);
397 const SvxLanguageItem *pLang = GetFormatAttr(RES_CHRATR_LANGUAGE);
398 OSL_ENSURE(pLang, "impossible");
399 LanguageType nDefault = pLang ? pLang->GetValue() : LANGUAGE_ENGLISH_US;
401 if (nLang != nDefault)
403 rField.SetAutomaticLanguage(false);
404 rField.SetLanguage(nLang);
405 bRet = true;
408 return bRet;
411 static OUString GetWordDefaultDateStringAsUS(SvNumberFormatter* pFormatter, LanguageType nLang)
413 //Get the system date in the correct final language layout, convert to
414 //a known language and modify the 2 digit year part to be 4 digit, and
415 //convert back to the correct language layout.
416 const sal_uInt32 nIndex = pFormatter->GetFormatIndex(NF_DATE_SYSTEM_SHORT, nLang);
418 SvNumberformat aFormat = *(pFormatter->GetEntry(nIndex));
419 aFormat.ConvertLanguage(*pFormatter, nLang, LANGUAGE_ENGLISH_US);
421 OUString sParams(aFormat.GetFormatstring());
422 // #i36594#
423 // Fix provided by mloiseleur@openoffice.org.
424 // A default date can have already 4 year digits, in some case
425 const sal_Int32 pos = sParams.indexOf("YYYY");
426 if ( pos == -1 )
428 sParams = sParams.replaceFirst("YY", "YYYY");
430 return sParams;
433 SvNumFormatType SwWW8ImplReader::GetTimeDatePara(std::u16string_view aStr, sal_uInt32& rFormat,
434 LanguageType &rLang, int nWhichDefault, bool bHijri)
436 bool bRTL = false;
437 if (m_xPlcxMan && !m_bVer67)
439 SprmResult aResult = m_xPlcxMan->HasCharSprm(0x85A);
440 if (aResult.pSprm && aResult.nRemainingData >= 1 && *aResult.pSprm)
441 bRTL = true;
443 TypedWhichId<SvxLanguageItem> eLang = bRTL ? RES_CHRATR_CTL_LANGUAGE : RES_CHRATR_LANGUAGE;
444 const SvxLanguageItem *pLang = GetFormatAttr(eLang);
445 OSL_ENSURE(pLang, "impossible");
446 rLang = pLang ? pLang->GetValue() : LANGUAGE_ENGLISH_US;
448 SvNumberFormatter* pFormatter = m_rDoc.GetNumberFormatter();
449 OUString sParams( FindPara( aStr, '@', '@' ) );// Date/Time
450 if (sParams.isEmpty())
452 bool bHasTime = false;
453 switch (nWhichDefault)
455 case ww::ePRINTDATE:
456 case ww::eSAVEDATE:
457 sParams = GetWordDefaultDateStringAsUS(pFormatter, rLang);
458 sParams += " HH:MM:SS AM/PM";
459 bHasTime = true;
460 break;
461 case ww::eCREATEDATE:
462 sParams += "DD/MM/YYYY HH:MM:SS";
463 bHasTime = true;
464 break;
465 default:
466 case ww::eDATE:
467 sParams = GetWordDefaultDateStringAsUS(pFormatter, rLang);
468 break;
471 if (bHijri)
472 sParams = "[~hijri]" + sParams;
474 sal_Int32 nCheckPos = 0;
475 SvNumFormatType nType = SvNumFormatType::DEFINED;
476 rFormat = 0;
478 pFormatter->PutandConvertEntry(sParams, nCheckPos, nType, rFormat,
479 LANGUAGE_ENGLISH_US, rLang, false);
481 return bHasTime ? SvNumFormatType::DATETIME : SvNumFormatType::DATE;
484 sal_uLong nFormatIdx =
485 sw::ms::MSDateTimeFormatToSwFormat(sParams, pFormatter, rLang, bHijri,
486 GetFib().m_lid);
487 SvNumFormatType nNumFormatType = SvNumFormatType::UNDEFINED;
488 if (nFormatIdx)
489 nNumFormatType = pFormatter->GetType(nFormatIdx);
490 rFormat = nFormatIdx;
492 return nNumFormatType;
495 // Fields
497 // Update respective fields after loading (currently references)
498 void SwWW8ImplReader::UpdateFields()
500 m_rDoc.getIDocumentState().SetUpdateExpFieldStat(true);
501 m_rDoc.SetInitDBFields(true); // Also update fields in the database
504 // Sanity check the PaM to see if it makes sense wrt sw::CalcBreaks
505 static bool SanityCheck(const SwPaM& rFieldPam)
507 SwNodeOffset const nEndNode(rFieldPam.End()->GetNodeIndex());
508 SwNodes const& rNodes(rFieldPam.GetPoint()->GetNodes());
509 SwNode *const pFinalNode(rNodes[nEndNode]);
510 if (pFinalNode->IsTextNode())
512 SwTextNode & rTextNode(*pFinalNode->GetTextNode());
513 return (rTextNode.Len() >= rFieldPam.End()->GetContentIndex());
515 return true;
518 sal_uInt16 SwWW8ImplReader::End_Field()
520 sal_uInt16 nRet = 0;
521 WW8PLCFx_FLD* pF = m_xPlcxMan->GetField();
522 OSL_ENSURE(pF, "WW8PLCFx_FLD - Pointer not available");
523 WW8_CP nCP = 0;
524 if (!pF || !pF->EndPosIsFieldEnd(nCP))
525 return nRet;
527 bool bUseEnhFields = officecfg::Office::Common::Filter::Microsoft::Import::ImportWWFieldsAsEnhancedFields::get();
529 OSL_ENSURE(!m_aFieldStack.empty(), "Empty field stack");
530 if (!m_aFieldStack.empty())
533 only hyperlinks currently need to be handled like this, for the other
534 cases we have inserted a field not an attribute with an unknown end
535 point
537 nRet = m_aFieldStack.back().mnFieldId;
538 switch (nRet)
540 case ww::eFORMTEXT:
541 if (bUseEnhFields && m_pPaM!=nullptr && m_pPaM->GetPoint()!=nullptr) {
542 SwPosition aEndPos = *m_pPaM->GetPoint();
543 SwPaM aFieldPam( m_aFieldStack.back().GetPtNode().GetNode(), m_aFieldStack.back().GetPtContent(), aEndPos.GetNode(), aEndPos.GetContentIndex());
545 IDocumentMarkAccess* pMarksAccess = m_rDoc.getIDocumentMarkAccess( );
546 Fieldmark *pFieldmark = SanityCheck(aFieldPam) ? pMarksAccess->makeFieldBookmark(
547 aFieldPam, m_aFieldStack.back().GetBookmarkName(), ODF_FORMTEXT,
548 aFieldPam.Start() /*same pos as start!*/ ) : nullptr;
549 OSL_ENSURE(pFieldmark!=nullptr, "hmmm; why was the bookmark not created?");
550 if (pFieldmark!=nullptr) {
551 // adapt redline positions to inserted field mark start
552 // dummy char (assume not necessary for end dummy char)
553 m_xRedlineStack->MoveAttrsFieldmarkInserted(*aFieldPam.Start());
554 const Fieldmark::parameter_map_t& rParametersToAdd = m_aFieldStack.back().getParameters();
555 pFieldmark->GetParameters()->insert(rParametersToAdd.begin(), rParametersToAdd.end());
558 break;
559 // Doing corresponding status management for TOX field, index field, hyperlink field and page reference field
560 case ww::eTOC://TOX
561 case ww::eINDEX://index
562 if (m_bLoadingTOXCache)
564 if (m_nEmbeddedTOXLevel > 0)
566 JoinNode(*m_pPaM);
567 --m_nEmbeddedTOXLevel;
569 else
571 m_aTOXEndCps.insert(nCP);
572 m_bLoadingTOXCache = false;
573 if ( m_pPaM->End() &&
574 m_pPaM->End()->GetNode().GetTextNode() &&
575 m_pPaM->End()->GetNode().GetTextNode()->Len() == 0 )
577 JoinNode(*m_pPaM);
579 else
581 m_bCareLastParaEndInToc = true;
584 if (m_oPosAfterTOC)
586 *m_pPaM = *m_oPosAfterTOC;
587 m_oPosAfterTOC.reset();
591 break;
592 case ww::ePAGEREF: //REF
593 if (m_bLoadingTOXCache && !m_bLoadingTOXHyperlink)
595 m_xCtrlStck->SetAttr(*m_pPaM->GetPoint(),RES_TXTATR_INETFMT);
597 break;
598 case ww::eHYPERLINK:
599 if (m_bLoadingTOXHyperlink)
600 m_bLoadingTOXHyperlink = false;
601 m_xCtrlStck->SetAttr(*m_pPaM->GetPoint(), RES_TXTATR_INETFMT);
602 break;
603 case ww::eMERGEINC:
604 case ww::eINCLUDETEXT:
606 //Move outside the section associated with this type of field
607 SwPosition aRestorePos(m_aFieldStack.back().maStartPos);
609 SwContentNode* pNd = aRestorePos.GetNode().GetContentNode();
610 sal_Int32 nMaxValidIndex = pNd ? pNd->Len() : 0;
611 if (aRestorePos.GetContentIndex() > nMaxValidIndex)
613 SAL_WARN("sw.ww8", "Attempt to restore to invalid content position");
614 aRestorePos.SetContent(nMaxValidIndex);
617 *m_pPaM->GetPoint() = std::move(aRestorePos);
618 break;
620 case ww::eIF: // IF-field
622 // conditional field parameters
623 OUString fieldDefinition = m_aFieldStack.back().GetBookmarkCode();
625 OUString paramCondition;
626 OUString paramTrue;
627 OUString paramFalse;
629 // ParseIfFieldDefinition expects: IF <some condition> "true result" "false result"
630 // while many fields include '\* MERGEFORMAT' after that.
631 // So first trim off the switches that are not supported anyway
632 sal_Int32 nLastIndex = fieldDefinition.lastIndexOf("\\*");
633 sal_Int32 nOtherIndex = fieldDefinition.lastIndexOf("\\#"); //number format
634 if (nOtherIndex > 0 && (nOtherIndex < nLastIndex || nLastIndex < 0))
635 nLastIndex = nOtherIndex;
636 nOtherIndex = fieldDefinition.lastIndexOf("\\@"); //date format
637 if (nOtherIndex > 0 && (nOtherIndex < nLastIndex || nLastIndex < 0))
638 nLastIndex = nOtherIndex;
639 nOtherIndex = fieldDefinition.lastIndexOf("\\!"); //locked result
640 if (nOtherIndex > 0 && (nOtherIndex < nLastIndex || nLastIndex < 0))
641 nLastIndex = nOtherIndex;
642 if (nLastIndex > 0)
643 fieldDefinition = fieldDefinition.copy(0, nLastIndex);
645 SwHiddenTextField::ParseIfFieldDefinition(fieldDefinition, paramCondition, paramTrue, paramFalse);
647 // create new field
648 SwFieldType* pFieldType = m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::HiddenText);
649 SwHiddenTextField aHTField(
650 static_cast<SwHiddenTextFieldType*>(pFieldType),
651 paramCondition,
652 paramTrue,
653 paramFalse,
654 SwFieldTypesEnum::ConditionalText);
656 // insert new field into document
657 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aHTField));
658 break;
660 default:
661 OUString aCode = m_aFieldStack.back().GetBookmarkCode();
662 if (!aCode.isEmpty() && !o3tl::starts_with(o3tl::trim(aCode), u"SHAPE"))
664 // Unhandled field with stored code
665 SwPosition aEndPos = *m_pPaM->GetPoint();
666 SwPaM aFieldPam(
667 m_aFieldStack.back().GetPtNode().GetNode(), m_aFieldStack.back().GetPtContent(),
668 aEndPos.GetNode(), aEndPos.GetContentIndex());
670 IDocumentMarkAccess* pMarksAccess = m_rDoc.getIDocumentMarkAccess( );
672 Fieldmark* pFieldmark = pMarksAccess->makeFieldBookmark(
673 aFieldPam,
674 m_aFieldStack.back().GetBookmarkName(),
675 ODF_UNHANDLED,
676 aFieldPam.Start() /*same pos as start!*/ );
677 if ( pFieldmark )
679 // adapt redline positions to inserted field mark start
680 // dummy char (assume not necessary for end dummy char)
681 m_xRedlineStack->MoveAttrsFieldmarkInserted(*aFieldPam.Start());
682 const Fieldmark::parameter_map_t& rParametersToAdd = m_aFieldStack.back().getParameters();
683 pFieldmark->GetParameters()->insert(rParametersToAdd.begin(), rParametersToAdd.end());
684 OUString sFieldId = OUString::number( m_aFieldStack.back().mnFieldId );
685 pFieldmark->GetParameters()->insert(
686 std::pair< OUString, uno::Any > (
687 ODF_ID_PARAM,
688 uno::Any( sFieldId ) ) );
689 pFieldmark->GetParameters()->insert(
690 std::pair< OUString, uno::Any > (
691 ODF_CODE_PARAM,
692 uno::Any( aCode ) ) );
694 if ( m_aFieldStack.back().mnObjLocFc > 0 )
696 // Store the OLE object as an internal link
697 OUString sOleId = "_" +
698 OUString::number( m_aFieldStack.back().mnObjLocFc );
700 rtl::Reference<SotStorage> xSrc0 = m_pStg->OpenSotStorage(SL::aObjectPool);
701 rtl::Reference<SotStorage> xSrc1 = xSrc0->OpenSotStorage( sOleId, StreamMode::READ );
703 // Store it now!
704 uno::Reference< embed::XStorage > xDocStg = GetDoc().GetDocStorage();
705 if (xDocStg.is())
707 uno::Reference< embed::XStorage > xOleStg = xDocStg->openStorageElement(
708 u"OLELinks"_ustr, embed::ElementModes::WRITE );
709 rtl::Reference<SotStorage> xObjDst = SotStorage::OpenOLEStorage( xOleStg, sOleId );
711 if ( xObjDst.is() )
713 xSrc1->CopyTo( xObjDst.get() );
715 if ( !xObjDst->GetError() )
716 xObjDst->Commit();
719 uno::Reference< embed::XTransactedObject > xTransact( xOleStg, uno::UNO_QUERY );
720 if ( xTransact.is() )
721 xTransact->commit();
724 // Store the OLE Id as a parameter
725 pFieldmark->GetParameters()->insert(
726 std::pair< OUString, uno::Any >(
727 ODF_OLE_PARAM, uno::Any( sOleId ) ) );
732 break;
734 m_aFieldStack.pop_back();
736 return nRet;
739 static bool AcceptableNestedField(sal_uInt16 nFieldCode)
741 switch (nFieldCode)
743 case ww::eINDEX: // allow recursive field in TOC...
744 case ww::eTOC: // allow recursive field in TOC...
745 case ww::eMERGEINC:
746 case ww::eINCLUDETEXT:
747 case ww::eAUTOTEXT:
748 case ww::eHYPERLINK:
749 // Accept AutoTextList field as nested field.
750 // Thus, the field result is imported as plain text.
751 case ww::eAUTOTEXTLIST:
752 // tdf#129247 CONTROL contains a nested SHAPE field in the result
753 case ww::eCONTROL:
754 return true;
755 default:
756 return false;
760 WW8FieldEntry::WW8FieldEntry(SwPosition const &rPos, sal_uInt16 nFieldId) noexcept
761 : maStartPos(rPos), mnFieldId(nFieldId), mnObjLocFc(0)
765 WW8FieldEntry::WW8FieldEntry(const WW8FieldEntry &rOther) noexcept
766 : maStartPos(rOther.maStartPos), mnFieldId(rOther.mnFieldId), mnObjLocFc(rOther.mnObjLocFc)
770 void WW8FieldEntry::Swap(WW8FieldEntry &rOther) noexcept
772 std::swap(maStartPos, rOther.maStartPos);
773 std::swap(mnFieldId, rOther.mnFieldId);
776 WW8FieldEntry &WW8FieldEntry::operator=(const WW8FieldEntry &rOther) noexcept
778 WW8FieldEntry aTemp(rOther);
779 Swap(aTemp);
780 return *this;
784 void WW8FieldEntry::SetBookmarkName(const OUString& bookmarkName)
786 msBookmarkName=bookmarkName;
789 void WW8FieldEntry::SetBookmarkType(const OUString& bookmarkType)
791 msMarkType=bookmarkType;
794 void WW8FieldEntry::SetBookmarkCode(const OUString& bookmarkCode)
796 msMarkCode = bookmarkCode;
800 // Read_Field reads a field or returns 0 if the field cannot be read,
801 // so that the calling function reads the field in text format.
802 // Returnvalue: Total length of field
803 tools::Long SwWW8ImplReader::Read_Field(WW8PLCFManResult* pRes)
805 typedef eF_ResT (SwWW8ImplReader::*FNReadField)( WW8FieldDesc*, OUString& );
806 constexpr sal_uInt16 eMax = 96;
807 static const FNReadField aWW8FieldTab[eMax+1] =
809 nullptr,
810 &SwWW8ImplReader::Read_F_Input,
811 nullptr,
812 &SwWW8ImplReader::Read_F_Ref, // 3
813 nullptr,
814 nullptr,
815 &SwWW8ImplReader::Read_F_Set, // 6
816 nullptr,
817 &SwWW8ImplReader::Read_F_Tox, // 8
818 nullptr,
819 &SwWW8ImplReader::Read_F_Styleref, // 10
820 nullptr,
821 &SwWW8ImplReader::Read_F_Seq, // 12
822 &SwWW8ImplReader::Read_F_Tox, // 13
823 &SwWW8ImplReader::Read_F_DocInfo, // 14
824 &SwWW8ImplReader::Read_F_DocInfo, // 15
825 &SwWW8ImplReader::Read_F_DocInfo, // 16
826 &SwWW8ImplReader::Read_F_Author, // 17
827 &SwWW8ImplReader::Read_F_DocInfo, // 18
828 &SwWW8ImplReader::Read_F_DocInfo, // 19
829 &SwWW8ImplReader::Read_F_DocInfo, // 20
830 &SwWW8ImplReader::Read_F_DocInfo, // 21
831 &SwWW8ImplReader::Read_F_DocInfo, // 22
832 &SwWW8ImplReader::Read_F_DocInfo, // 23
833 &SwWW8ImplReader::Read_F_DocInfo, // 24
834 &SwWW8ImplReader::Read_F_DocInfo, // 25
835 &SwWW8ImplReader::Read_F_Num, // 26
836 &SwWW8ImplReader::Read_F_Num, // 27
837 &SwWW8ImplReader::Read_F_Num, // 28
838 &SwWW8ImplReader::Read_F_FileName, // 29
839 &SwWW8ImplReader::Read_F_TemplName, // 30
840 &SwWW8ImplReader::Read_F_DateTime, // 31
841 &SwWW8ImplReader::Read_F_DateTime, // 32
842 &SwWW8ImplReader::Read_F_CurPage, // 33
843 nullptr,
844 nullptr,
845 &SwWW8ImplReader::Read_F_IncludeText, // 36
846 &SwWW8ImplReader::Read_F_PgRef, // 37
847 &SwWW8ImplReader::Read_F_InputVar, // 38
848 &SwWW8ImplReader::Read_F_Input, // 39
849 nullptr,
850 &SwWW8ImplReader::Read_F_DBNext, // 41
851 nullptr,
852 nullptr,
853 &SwWW8ImplReader::Read_F_DBNum, // 44
854 nullptr,
855 nullptr,
856 nullptr,
857 nullptr,
858 &SwWW8ImplReader::Read_F_Equation, // 49
859 nullptr,
860 &SwWW8ImplReader::Read_F_Macro, // 51
861 &SwWW8ImplReader::Read_F_ANumber, // 52
862 &SwWW8ImplReader::Read_F_ANumber, // 53
863 &SwWW8ImplReader::Read_F_ANumber, // 54
864 nullptr,
866 nullptr, // 56
868 &SwWW8ImplReader::Read_F_Symbol, // 57
869 &SwWW8ImplReader::Read_F_Embedd, // 58
870 &SwWW8ImplReader::Read_F_DBField, // 59
871 nullptr,
872 nullptr,
873 nullptr,
874 nullptr,
875 &SwWW8ImplReader::Read_F_DocInfo, // 64 - DOCVARIABLE
876 nullptr,
877 nullptr,
878 &SwWW8ImplReader::Read_F_IncludePicture, // 67
879 &SwWW8ImplReader::Read_F_IncludeText, // 68
880 nullptr,
881 &SwWW8ImplReader::Read_F_FormTextBox, // 70
882 &SwWW8ImplReader::Read_F_FormCheckBox, // 71
883 &SwWW8ImplReader::Read_F_NoteReference, // 72
884 nullptr, /*&SwWW8ImplReader::Read_F_Tox*/
885 nullptr,
886 nullptr,
887 nullptr,
888 nullptr,
889 nullptr,
890 nullptr,
891 nullptr,
892 nullptr,
893 nullptr,
894 &SwWW8ImplReader::Read_F_FormListBox, // 83
895 nullptr, // 84
896 &SwWW8ImplReader::Read_F_DocInfo, // 85
897 nullptr, // 86
898 &SwWW8ImplReader::Read_F_OCX, // 87
899 &SwWW8ImplReader::Read_F_Hyperlink, // 88
900 nullptr, // 89
901 nullptr, // 90
902 &SwWW8ImplReader::Read_F_HTMLControl, // 91
903 nullptr, // 92
904 nullptr, // 93
905 nullptr, // 94
906 &SwWW8ImplReader::Read_F_Shape, // 95
907 nullptr // eMax - Dummy empty method
909 OSL_ENSURE( SAL_N_ELEMENTS( aWW8FieldTab ) == eMax+1, "FieldFunc table not right" );
911 WW8PLCFx_FLD* pF = m_xPlcxMan->GetField();
912 OSL_ENSURE(pF, "WW8PLCFx_FLD - Pointer not available");
914 if (!pF || !pF->StartPosIsFieldStart())
915 return 0;
917 bool bNested = false;
918 if (!m_aFieldStack.empty())
920 bNested = std::any_of(m_aFieldStack.cbegin(), m_aFieldStack.cend(),
921 [](const WW8FieldEntry& aField) { return !AcceptableNestedField(aField.mnFieldId); });
924 WW8FieldDesc aF;
925 bool bOk = pF->GetPara(pRes->nCp2OrIdx, aF);
927 OSL_ENSURE(bOk, "WW8: Bad Field!");
928 if (aF.nId == 33) aF.bCodeNest=false; // do not recurse into nested page fields
929 bool bCodeNest = aF.bCodeNest;
930 if ( aF.nId == 6 ) bCodeNest = false; // We can handle them and lose the inner data
931 if (aF.nId == 70) bCodeNest = false; // need to import 0x01 in FORMTEXT
933 m_aFieldStack.emplace_back(*m_pPaM->GetPoint(), aF.nId);
935 if (bNested)
936 return 0;
938 sal_uInt16 n = (aF.nId <= eMax) ? aF.nId : eMax;
939 sal_uInt32 nI = n / 32U; // # of sal_uInt32
941 static_assert(FieldTagSize == SAL_N_ELEMENTS(m_nFieldTagAlways) &&
942 FieldTagSize == SAL_N_ELEMENTS(m_nFieldTagBad),
943 "m_nFieldTagAlways and m_nFieldTagBad should have the same FieldTagSize num of elements");
945 if (nI >= FieldTagSize)
946 { // if indexes larger than 95 are needed, then a new configuration
947 // item has to be added, and nFieldTagAlways/nFieldTagBad expanded!
948 return aF.nLen;
951 sal_uInt32 nMask = 1 << ( n % 32 ); // Mask for bits
953 if( m_nFieldTagAlways[nI] & nMask ) // Flag: Tag it
954 return Read_F_Tag( &aF ); // Result not as text
956 if( !bOk || !aF.nId ) // Field corrupted
957 return aF.nLen; // -> ignore
959 if( aF.nId > eMax - 1) // WW: Nested Field
961 if( m_nFieldTagBad[nI] & nMask ) // Flag: Tag it when bad
962 return Read_F_Tag( &aF ); // Result not as text
963 else
964 return aF.nLen;
967 //Only one type of field (hyperlink) in drawing textboxes exists
968 if (aF.nId != 88 && m_xPlcxMan->GetDoingDrawTextBox())
969 return aF.nLen;
971 bool bHasHandler = aWW8FieldTab[aF.nId] != nullptr;
972 if (aF.nId == 10) // STYLEREF
974 bool bHandledByChapter = false;
975 sal_uInt64 nOldPos = m_pStrm->Tell();
976 OUString aStr;
977 aF.nLCode = m_xSBase->WW8ReadString(*m_pStrm, aStr, m_xPlcxMan->GetCpOfs() + aF.nSCode, aF.nLCode, m_eTextCharSet);
978 m_pStrm->Seek(nOldPos);
980 WW8ReadFieldParams aReadParam(aStr);
981 sal_Int32 nRet = aReadParam.SkipToNextToken();
982 if (nRet == -2 && !aReadParam.GetResult().isEmpty())
983 // Single numeric argument: this can be handled by SwChapterField.
984 bHandledByChapter = rtl::isAsciiDigit(aReadParam.GetResult()[0]);
986 if (bHandledByChapter)
988 nRet = aReadParam.SkipToNextToken();
989 // Handle using SwChapterField only in case there is no \[a-z]
990 // switch after the field argument.
991 bHasHandler = nRet < 0 || nRet == '*';
995 // no routine available
996 if (!bHasHandler || bCodeNest)
998 if( m_nFieldTagBad[nI] & nMask ) // Flag: Tag it when bad
999 return Read_F_Tag( &aF ); // Result not as text
1000 // only read result
1001 if (aF.bResNest && !AcceptableNestedField(aF.nId))
1002 return aF.nLen; // Result nested -> unusable
1004 sal_uInt64 nOldPos = m_pStrm->Tell();
1005 OUString aStr;
1006 aF.nLCode = m_xSBase->WW8ReadString( *m_pStrm, aStr, m_xPlcxMan->GetCpOfs()+
1007 aF.nSCode, aF.nLCode, m_eTextCharSet );
1008 m_pStrm->Seek( nOldPos );
1010 // field codes which contain '/' or '.' are not displayed in WinWord
1011 // skip if it is formula field or found space before. see #i119446, #i119585.
1012 const sal_Int32 nDotPos = aStr.indexOf('.');
1013 const sal_Int32 nSlashPos = aStr.indexOf('/');
1014 sal_Int32 nSpacePos = aStr.indexOf( ' ', 1 );
1015 if ( nSpacePos<0 )
1016 nSpacePos = aStr.getLength();
1018 if ( ( aStr.getLength() <= 1 || aStr[1] != '=') &&
1019 (( nDotPos>=0 && nDotPos < nSpacePos ) ||
1020 ( nSlashPos>=0 && nSlashPos < nSpacePos )))
1021 return aF.nLen;
1022 else
1024 // Link fields aren't supported, but they are bound to an OLE object
1025 // that needs to be roundtripped
1026 if ( aF.nId == 56 )
1027 m_bEmbeddObj = true;
1028 // Field not supported: store the field code for later use
1029 m_aFieldStack.back().SetBookmarkCode( aStr );
1031 if (aF.nId == ww::eIF)
1033 // In MS Word, the IF field is editable and requires a manual refresh
1034 // so the last, saved result might not match either of the true or false options.
1035 // But in LO the field is automatically updated and not editable,
1036 // so the previous result is of no value to import since it could never be seen.
1037 return aF.nLen;
1040 return aF.nLen - aF.nLRes - 1; // skipped too many, the resulted field will be read like main text
1043 else
1044 { // read field
1045 auto nOldPos = m_pStrm->Tell();
1046 OUString aStr;
1047 if ( aF.nId == 6 && aF.bCodeNest )
1049 // TODO Extract the whole code string using the nested codes
1050 aF.nLCode = m_xSBase->WW8ReadString( *m_pStrm, aStr, m_xPlcxMan->GetCpOfs() +
1051 aF.nSCode, aF.nSRes - aF.nSCode - 1, m_eTextCharSet );
1053 else
1055 aF.nLCode = m_xSBase->WW8ReadString( *m_pStrm, aStr, m_xPlcxMan->GetCpOfs()+
1056 aF.nSCode, aF.nLCode, m_eTextCharSet );
1059 // #i51312# - graphics inside field code not supported by Writer.
1060 // Thus, delete character 0x01, which stands for such a graphic.
1061 if (aF.nId==51) //#i56768# only do it for the MACROBUTTON field, since DropListFields need the 0x01.
1063 aStr = aStr.replaceAll("\x01", "");
1066 eF_ResT eRes = (this->*aWW8FieldTab[aF.nId])( &aF, aStr );
1067 m_pStrm->Seek(nOldPos);
1069 switch ( eRes )
1071 case eF_ResT::OK:
1072 return aF.nLen;
1073 case eF_ResT::TEXT:
1074 // skipped too many, the resulted field will be read like main text
1075 // attributes can start at char 0x14 so skip one
1076 // char more back == "-2"
1077 if (aF.nLRes)
1078 return aF.nLen - aF.nLRes - 2;
1079 else
1080 return aF.nLen;
1081 case eF_ResT::TAGIGN:
1082 if ( m_nFieldTagBad[nI] & nMask ) // Flag: Tag bad
1083 return Read_F_Tag( &aF ); // Tag it
1084 return aF.nLen; // or ignore
1085 case eF_ResT::READ_FSPA:
1086 return aF.nLen - aF.nLRes - 2; // position on char 1
1087 default:
1088 return aF.nLen; // ignore
1093 // Tag fields
1095 // MakeTagString() returns the position of the first CR / end of line / page break
1096 // in pText and converts only up to this point.
1097 // If none of these special characters is found, the function returns 0.
1098 void SwWW8ImplReader::MakeTagString( OUString& rStr, const OUString& rOrg )
1100 bool bAllowCr = SwFltGetFlag( m_nFieldFlags, SwFltControlStack::TAGS_IN_TEXT )
1101 || SwFltGetFlag( m_nFieldFlags, SwFltControlStack::ALLOW_FLD_CR );
1102 sal_Unicode cChar;
1103 rStr = rOrg;
1105 for( sal_Int32 nI = 0;
1106 nI < rStr.getLength() && rStr.getLength() < (MAX_FIELDLEN - 4); ++nI )
1108 bool bSetAsHex = false;
1109 cChar = rStr[ nI ];
1110 switch( cChar )
1112 case 132: // Exchange typographical quotation marks for normal ones
1113 case 148:
1114 case 147:
1115 rStr = rStr.replaceAt( nI, 1, u"\"" );
1116 break;
1117 case 19:
1118 rStr = rStr.replaceAt( nI, 1, u"{" );
1119 break; // 19..21 to {|}
1120 case 20:
1121 rStr = rStr.replaceAt( nI, 1, u"|" );
1122 break;
1123 case 21:
1124 rStr = rStr.replaceAt( nI, 1, u"}" );
1125 break;
1126 case '\\': // Tag \{|} with \ ...
1127 case '{':
1128 case '|':
1129 case '}':
1130 rStr = rStr.replaceAt( nI, 0, u"\\" );
1131 ++nI;
1132 break;
1133 case 0x0b:
1134 case 0x0c:
1135 case 0x0d:
1136 if( bAllowCr )
1137 rStr = rStr.replaceAt( nI, 1, u"\n" );
1138 else
1139 bSetAsHex = true;
1140 break;
1141 case 0xFE:
1142 case 0xFF:
1143 bSetAsHex = true;
1144 break;
1145 default:
1146 bSetAsHex = 0x20 > cChar;
1147 break;
1150 if( bSetAsHex )
1152 //all Hex-Numbers with \x before
1153 OUString sTmp( u"\\x"_ustr );
1154 if( cChar < 0x10 )
1155 sTmp += "0";
1156 sTmp += OUString::number( cChar, 16 );
1157 rStr = rStr.replaceAt( nI, 1 , sTmp );
1158 nI += sTmp.getLength() - 1;
1162 if( rStr.getLength() > (MAX_FIELDLEN - 4))
1163 rStr = rStr.copy( 0, MAX_FIELDLEN - 4 );
1166 void SwWW8ImplReader::InsertTagField( const sal_uInt16 nId, const OUString& rTagText )
1168 OUString aName(u"WwFieldTag"_ustr);
1169 if( SwFltGetFlag( m_nFieldFlags, SwFltControlStack::TAGS_DO_ID ) ) // Number?
1170 aName += OUString::number( nId ); // return it?
1172 if( SwFltGetFlag(m_nFieldFlags, SwFltControlStack::TAGS_IN_TEXT))
1174 aName += rTagText; // tag as text
1175 m_rDoc.getIDocumentContentOperations().InsertString(*m_pPaM, aName,
1176 SwInsertFlags::NOHINTEXPAND);
1178 else
1179 { // tag normally
1181 SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType(
1182 SwSetExpFieldType( &m_rDoc, aName, nsSwGetSetExpType::GSE_STRING ) );
1183 SwSetExpField aField( static_cast<SwSetExpFieldType*>(pFT), rTagText ); // SUB_INVISIBLE
1184 sal_uInt16 nSubType = ( SwFltGetFlag( m_nFieldFlags, SwFltControlStack::TAGS_VISIBLE ) ) ? 0 : nsSwExtendedSubType::SUB_INVISIBLE;
1185 aField.SetSubType(nSubType | nsSwGetSetExpType::GSE_STRING);
1187 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1191 WW8_CP SwWW8ImplReader::Read_F_Tag( WW8FieldDesc* pF )
1193 sal_uInt64 nOldPos = m_pStrm->Tell();
1195 WW8_CP nStart = pF->nSCode - 1; // starting with 0x19
1196 WW8_CP nL = pF->nLen; // Total length with result and nest
1197 if( nL > MAX_FIELDLEN )
1198 nL = MAX_FIELDLEN; // MaxLength, by quoting
1199 // max. 4 times as big
1200 OUString sFText;
1201 m_xSBase->WW8ReadString( *m_pStrm, sFText,
1202 m_xPlcxMan->GetCpOfs() + nStart, nL, m_eStructCharSet);
1204 OUString aTagText;
1205 MakeTagString( aTagText, sFText );
1206 InsertTagField( pF->nId, aTagText );
1208 m_pStrm->Seek( nOldPos );
1209 return pF->nLen;
1212 // normal fields
1214 eF_ResT SwWW8ImplReader::Read_F_Input( WW8FieldDesc* pF, OUString& rStr )
1216 OUString aDef;
1217 OUString aQ;
1218 WW8ReadFieldParams aReadParam( rStr );
1219 for (;;)
1221 const sal_Int32 nRet = aReadParam.SkipToNextToken();
1222 if ( nRet==-1 )
1223 break;
1224 switch( nRet )
1226 case -2:
1227 if( aQ.isEmpty() )
1228 aQ = aReadParam.GetResult();
1229 break;
1230 case 'd':
1231 case 'D':
1232 if ( aReadParam.GoToTokenParam() )
1233 aDef = aReadParam.GetResult();
1234 break;
1237 if( aDef.isEmpty() )
1238 aDef = GetFieldResult( pF );
1240 if ( pF->nId != 0x01 ) // 0x01 fields have no result
1242 SwInputField aField( static_cast<SwInputFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Input )),
1243 aDef, aQ, INP_TXT, 0, false );
1244 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1247 return eF_ResT::OK;
1250 // GetFieldResult allocates a string and reads the resulted field
1251 OUString SwWW8ImplReader::GetFieldResult( WW8FieldDesc const * pF )
1253 sal_uInt64 nOldPos = m_pStrm->Tell();
1255 WW8_CP nStart = pF->nSRes; // result start
1256 WW8_CP nL = pF->nLRes; // result length
1257 if( !nL )
1258 return OUString(); // no result
1260 if( nL > MAX_FIELDLEN )
1261 nL = MAX_FIELDLEN; // MaxLength, by quoting
1262 // max. 4 times as big
1264 OUString sRes;
1265 m_xSBase->WW8ReadString( *m_pStrm, sRes, m_xPlcxMan->GetCpOfs() + nStart,
1266 nL, m_eStructCharSet );
1268 m_pStrm->Seek( nOldPos );
1270 //replace both CR 0x0D and VT 0x0B with LF 0x0A
1271 // at least in the cases where the result is added to an SwInputField
1272 // there must not be control characters in it
1273 OUStringBuffer buf(sRes.getLength());
1274 for (sal_Int32 i = 0; i < sRes.getLength(); ++i)
1276 sal_Unicode const ch(sRes[i]);
1277 if (!linguistic::IsControlChar(ch))
1279 buf.append(ch);
1281 else
1283 switch (ch)
1285 case 0x0B:
1286 case '\r':
1287 buf.append('\n');
1288 break;
1289 case '\n':
1290 case '\t':
1291 buf.append(ch);
1292 break;
1293 default:
1294 SAL_INFO("sw.ww8", "GetFieldResult(): filtering control character");
1295 break;
1299 return buf.makeStringAndClear();
1303 Bookmarks can be set with fields SET and ASK, and they can be referenced with
1304 REF. When set, they behave like variables in writer, otherwise they behave
1305 like normal bookmarks. We can check whether we should use a show variable
1306 instead of a normal bookmark ref by converting to "show variable" at the end
1307 of the document those refs which look for the content of a bookmark but whose
1308 bookmarks were set with SET or ASK. (See SwWW8FltRefStack)
1310 The other piece of the puzzle is that refs that point to the "location" of the
1311 bookmark will in word actually point to the last location where the bookmark
1312 was set with SET or ASK, not the actual bookmark. This is only noticeable when
1313 a document sets the bookmark more than once. This is because word places the
1314 true bookmark at the location of the last set, but the refs will display the
1315 position of the first set before the ref.
1317 So what we will do is
1319 1) keep a list of all bookmarks that were set, any bookmark names mentioned
1320 here that are referred by content will be converted to show variables.
1322 2) create pseudo bookmarks for every position that a bookmark is set with SET
1323 or ASK but has no existing bookmark. We can then keep a map from the original
1324 bookmark name to the new one. As we parse the document new pseudo names will
1325 replace the older ones, so the map always contains the bookmark of the
1326 location that msword itself would use.
1328 3) word's bookmarks are case insensitive, writers are not. So we need to
1329 map case different versions together, regardless of whether they are
1330 variables or not.
1332 4) when a reference is (first) SET or ASK, the bookmark associated with it
1333 is placed around the 0x14 0x15 result part of the field. We will fiddle
1334 the placement to be the writer equivalent of directly before and after
1335 the field, which gives the same effect and meaning, to do so we must
1336 get any bookmarks in the field range, and begin them immediately before
1337 the set/ask field, and end them directly afterwards. MapBookmarkVariables
1338 returns an identifier of the bookmark attribute to close after inserting
1339 the appropriate set/ask field.
1341 tools::Long SwWW8ImplReader::MapBookmarkVariables(const WW8FieldDesc* pF,
1342 OUString &rOrigName, const OUString &rData)
1344 OSL_ENSURE(m_xPlcxMan, "No pPlcxMan");
1345 tools::Long nNo;
1347 If there was no bookmark associated with this set field, then we create a
1348 pseudo one and insert it in the document.
1350 sal_uInt16 nIndex;
1351 m_xPlcxMan->GetBook()->MapName(rOrigName);
1352 OUString sName = m_xPlcxMan->GetBook()->GetBookmark(
1353 pF->nSCode, pF->nSCode + pF->nLen, nIndex);
1354 if (!sName.isEmpty())
1356 m_xPlcxMan->GetBook()->SetStatus(nIndex, BOOK_IGNORE);
1357 nNo = nIndex;
1359 else
1361 nNo = m_xReffingStck->m_aFieldVarNames.size()+1;
1362 sName = "WWSetBkmk" + OUString::number(nNo);
1363 nNo += m_xPlcxMan->GetBook()->GetIMax();
1365 m_xReffedStck->NewAttr(*m_pPaM->GetPoint(),
1366 SwFltBookmark( BookmarkToWriter(sName), rData, nNo ));
1367 m_xReffingStck->m_aFieldVarNames[rOrigName] = sName;
1368 return nNo;
1372 Word can set a bookmark with set or with ask, such a bookmark is equivalent to
1373 our variables, but until the end of a document we cannot be sure if a bookmark
1374 is a variable or not, at the end we will have a list of reference names which
1375 were set or asked, all bookmarks using the content of those bookmarks are
1376 converted to show variables, those that reference the position of the field
1377 can be left as references, because a bookmark is also inserted at the position
1378 of a set or ask field, either by word, or in some special cases by the import
1379 filter itself.
1381 SwFltStackEntry *SwWW8FltRefStack::RefToVar(const SwField* pField,
1382 SwFltStackEntry &rEntry)
1384 SwFltStackEntry *pRet=nullptr;
1385 if (pField && SwFieldIds::GetRef == pField->Which())
1387 //Get the name of the ref field, and see if actually a variable
1388 const OUString sName = pField->GetPar1();
1389 std::map<OUString, OUString, SwWW8::ltstr>::const_iterator
1390 aResult = m_aFieldVarNames.find(sName);
1392 if (aResult != m_aFieldVarNames.end())
1394 SwGetExpField aField( static_cast<SwGetExpFieldType*>(
1395 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::GetExp)), sName, nsSwGetSetExpType::GSE_STRING, 0);
1396 SwFormatField aTmp(aField);
1397 rEntry.m_pAttr.reset( aTmp.Clone() );
1398 pRet = &rEntry;
1401 return pRet;
1404 OUString SwWW8ImplReader::GetMappedBookmark(std::u16string_view rOrigName)
1406 OUString sName(BookmarkToWriter(rOrigName));
1407 OSL_ENSURE(m_xPlcxMan, "no pPlcxMan");
1408 m_xPlcxMan->GetBook()->MapName(sName);
1410 //See if there has been a variable set with this name, if so get
1411 //the pseudo bookmark name that was set with it.
1412 std::map<OUString, OUString, SwWW8::ltstr>::const_iterator aResult =
1413 m_xReffingStck->m_aFieldVarNames.find(sName);
1415 return (aResult == m_xReffingStck->m_aFieldVarNames.end())
1416 ? sName : (*aResult).second;
1419 // "ASK"
1420 eF_ResT SwWW8ImplReader::Read_F_InputVar( WW8FieldDesc* pF, OUString& rStr )
1422 OUString sOrigName, aQ;
1423 OUString aDef;
1424 WW8ReadFieldParams aReadParam( rStr );
1425 for (;;)
1427 const sal_Int32 nRet = aReadParam.SkipToNextToken();
1428 if ( nRet==-1 )
1429 break;
1430 switch( nRet )
1432 case -2:
1433 if (sOrigName.isEmpty())
1434 sOrigName = aReadParam.GetResult();
1435 else if (aQ.isEmpty())
1436 aQ = aReadParam.GetResult();
1437 break;
1438 case 'd':
1439 case 'D':
1440 if ( aReadParam.GoToTokenParam() )
1441 aDef = aReadParam.GetResult();
1442 break;
1446 if (sOrigName.isEmpty())
1447 return eF_ResT::TAGIGN; // does not make sense without textmark
1449 const OUString aResult(GetFieldResult(pF));
1451 //#i24377#, munge Default Text into title as we have only one slot
1452 //available for aResult and aDef otherwise
1453 if (!aDef.isEmpty())
1455 if (!aQ.isEmpty())
1456 aQ += " - ";
1457 aQ += aDef;
1460 const tools::Long nNo = MapBookmarkVariables(pF, sOrigName, aResult);
1462 SwSetExpFieldType* pFT = static_cast<SwSetExpFieldType*>(m_rDoc.getIDocumentFieldsAccess().InsertFieldType(
1463 SwSetExpFieldType(&m_rDoc, sOrigName, nsSwGetSetExpType::GSE_STRING)));
1464 SwSetExpField aField(pFT, aResult);
1465 aField.SetSubType(nsSwExtendedSubType::SUB_INVISIBLE | nsSwGetSetExpType::GSE_STRING);
1466 aField.SetInputFlag(true);
1467 aField.SetPromptText( aQ );
1469 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1471 m_xReffedStck->SetAttr(*m_pPaM->GetPoint(), RES_FLTR_BOOKMARK, true, nNo);
1472 return eF_ResT::OK;
1475 // "AUTONR"
1476 eF_ResT SwWW8ImplReader::Read_F_ANumber( WW8FieldDesc*, OUString& rStr )
1478 if( !m_pNumFieldType ){ // 1st time
1479 SwSetExpFieldType aT( &m_rDoc, u"AutoNr"_ustr, nsSwGetSetExpType::GSE_SEQ );
1480 m_pNumFieldType = static_cast<SwSetExpFieldType*>(m_rDoc.getIDocumentFieldsAccess().InsertFieldType( aT ));
1482 SwSetExpField aField( m_pNumFieldType, OUString(), GetNumberPara( rStr ) );
1483 aField.SetValue( ++m_nFieldNum, nullptr );
1484 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1485 return eF_ResT::OK;
1488 // "SEQ"
1489 eF_ResT SwWW8ImplReader::Read_F_Seq( WW8FieldDesc*, OUString& rStr )
1491 OUString aSequenceName;
1492 OUString aBook;
1493 bool bHidden = false;
1494 bool bFormat = false;
1495 bool bCountOn = true;
1496 OUString sStart;
1497 SvxNumType eNumFormat = SVX_NUM_ARABIC;
1498 WW8ReadFieldParams aReadParam( rStr );
1499 for (;;)
1501 const sal_Int32 nRet = aReadParam.SkipToNextToken();
1502 if ( nRet==-1 )
1503 break;
1504 switch( nRet )
1506 case -2:
1507 if( aSequenceName.isEmpty() )
1508 aSequenceName = aReadParam.GetResult();
1509 else if( aBook.isEmpty() )
1510 aBook = aReadParam.GetResult();
1511 break;
1513 case 'h':
1514 if( !bFormat )
1515 bHidden = true; // activate hidden flag
1516 break;
1518 case '*':
1519 bFormat = true; // activate format flag
1520 if ( aReadParam.SkipToNextToken()!=-2 )
1521 break;
1522 if ( aReadParam.GetResult()!="MERGEFORMAT" && aReadParam.GetResult()!="CHARFORMAT" )
1523 eNumFormat = GetNumTypeFromName( aReadParam.GetResult() );
1524 break;
1526 case 'r':
1527 bCountOn = false;
1528 if ( aReadParam.SkipToNextToken()==-2 )
1529 sStart = aReadParam.GetResult();
1530 break;
1532 case 'c':
1533 bCountOn = false;
1534 break;
1536 case 'n':
1537 bCountOn = true; // Increase value by one (default)
1538 break;
1540 case 's': // Outline Level
1541 //#i19682, what I have to do with this value?
1542 break;
1545 if (aSequenceName.isEmpty() && aBook.isEmpty())
1546 return eF_ResT::TAGIGN;
1548 SwSetExpFieldType* pFT = static_cast<SwSetExpFieldType*>(m_rDoc.getIDocumentFieldsAccess().InsertFieldType(
1549 SwSetExpFieldType( &m_rDoc, aSequenceName, nsSwGetSetExpType::GSE_SEQ ) ) );
1550 SwSetExpField aField( pFT, OUString(), eNumFormat );
1552 //#i120654# Add bHidden for /h flag (/h: Hide the field result.)
1553 if (bHidden)
1554 aField.SetSubType(aField.GetSubType() | nsSwExtendedSubType::SUB_INVISIBLE);
1556 if (!sStart.isEmpty())
1557 aField.SetFormula( aSequenceName + "=" + sStart );
1558 else if (!bCountOn)
1559 aField.SetFormula(aSequenceName);
1561 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
1562 return eF_ResT::OK;
1565 eF_ResT SwWW8ImplReader::Read_F_Styleref(WW8FieldDesc*, OUString& rString)
1567 WW8ReadFieldParams aReadParam(rString);
1568 sal_Int32 nRet = aReadParam.SkipToNextToken();
1569 if (nRet != -2)
1570 // \param was found, not normal text.
1571 return eF_ResT::TAGIGN;
1573 OUString aResult = aReadParam.GetResult();
1574 sal_Int32 nResult = aResult.toInt32();
1575 if (nResult < 1)
1576 return eF_ResT::TAGIGN;
1578 SwFieldType* pFieldType = m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Chapter);
1579 SwChapterField aField(static_cast<SwChapterFieldType*>(pFieldType), CF_TITLE);
1580 aField.SetLevel(nResult - 1);
1581 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
1583 return eF_ResT::OK;
1586 eF_ResT SwWW8ImplReader::Read_F_DocInfo( WW8FieldDesc* pF, OUString& rStr )
1588 sal_uInt16 nSub=0;
1589 // RegInfoFormat, DefaultFormat for DocInfoFields
1590 sal_uInt16 nReg = DI_SUB_AUTHOR;
1591 bool bDateTime = false;
1592 const sal_uInt16 nFldLock = (pF->nOpt & 0x10) ? DI_SUB_FIXED : 0;
1594 if( 85 == pF->nId )
1596 OUString aDocProperty;
1597 WW8ReadFieldParams aReadParam( rStr );
1598 for (;;)
1600 const sal_Int32 nRet = aReadParam.SkipToNextToken();
1601 if ( nRet==-1 )
1602 break;
1603 switch( nRet )
1605 case -2:
1606 if( aDocProperty.isEmpty() )
1607 aDocProperty = aReadParam.GetResult();
1608 break;
1609 case '*':
1610 //Skip over MERGEFORMAT
1611 (void)aReadParam.SkipToNextToken();
1612 break;
1616 aDocProperty = aDocProperty.replaceAll("\"", "");
1619 There are up to 26 fields that may be meant by 'DocumentProperty'.
1620 Which of them is to be inserted here ?
1621 This Problem can only be solved by implementing a name matching
1622 method that compares the given Parameter String with the four
1623 possible name sets (english, german, french, spanish)
1626 static const char* aName10 = "\x0F"; // SW field code
1627 static const char* aName11 // German
1628 = "TITEL";
1629 static const char* aName12 // French
1630 = "TITRE";
1631 static const char* aName13 // English
1632 = "TITLE";
1633 static const char* aName14 // Spanish
1634 = "TITRO";
1635 static const char* aName20 = "\x15"; // SW field code
1636 static const char* aName21 // German
1637 = "ERSTELLDATUM";
1638 static const char* aName22 // French
1639 = "CR\xC9\xC9";
1640 static const char* aName23 // English
1641 = "CREATED";
1642 static const char* aName24 // Spanish
1643 = "CREADO";
1644 static const char* aName30 = "\x16"; // SW field code
1645 static const char* aName31 // German
1646 = "ZULETZTGESPEICHERTZEIT";
1647 static const char* aName32 // French
1648 = "DERNIERENREGISTREMENT";
1649 static const char* aName33 // English
1650 = "SAVED";
1651 static const char* aName34 // Spanish
1652 = "MODIFICADO";
1653 static const char* aName40 = "\x17"; // SW field code
1654 static const char* aName41 // German
1655 = "ZULETZTGEDRUCKT";
1656 static const char* aName42 // French
1657 = "DERNI\xC8" "REIMPRESSION";
1658 static const char* aName43 // English
1659 = "LASTPRINTED";
1660 static const char* aName44 // Spanish
1661 = "HUPS PUPS";
1662 static const char* aName50 = "\x18"; // SW field code
1663 static const char* aName51 // German
1664 = "\xDC" "BERARBEITUNGSNUMMER";
1665 static const char* aName52 // French
1666 = "NUM\xC9" "RODEREVISION";
1667 static const char* aName53 // English
1668 = "REVISIONNUMBER";
1669 static const char* aName54 // Spanish
1670 = "SNUBBEL BUBBEL";
1671 static const sal_uInt16 nFieldCnt = 5;
1673 // additional fields are to be coded soon!
1675 static const sal_uInt16 nLangCnt = 4;
1676 static const char *aNameSet_26[nFieldCnt][nLangCnt+1] =
1678 {aName10, aName11, aName12, aName13, aName14},
1679 {aName20, aName21, aName22, aName23, aName24},
1680 {aName30, aName31, aName32, aName33, aName34},
1681 {aName40, aName41, aName42, aName43, aName44},
1682 {aName50, aName51, aName52, aName53, aName54}
1685 bool bFieldFound= false;
1686 sal_uInt16 nFIdx;
1687 for(sal_uInt16 nLIdx=1; !bFieldFound && (nLangCnt > nLIdx); ++nLIdx)
1689 for(nFIdx = 0; !bFieldFound && (nFieldCnt > nFIdx); ++nFIdx)
1691 if( aDocProperty == OUString( aNameSet_26[nFIdx][nLIdx], strlen(aNameSet_26[nFIdx][nLIdx]),
1692 RTL_TEXTENCODING_MS_1252 ) )
1694 bFieldFound = true;
1695 pF->nId = aNameSet_26[nFIdx][0][0];
1700 if( !bFieldFound )
1702 // LO always automatically updates a DocInfo field from the File-Properties-Custom Prop
1703 // while MS Word requires the user to manually refresh the field (with F9).
1704 // In other words, Word lets the field to be out of sync with the controlling variable.
1705 // Marking as FIXEDFLD solves the automatic replacement problem, but of course prevents
1706 // Writer from making any changes, even on an F9 refresh.
1707 // TODO: Extend LO to allow a linked field that doesn't automatically update.
1708 IDocumentContentOperations& rIDCO(m_rDoc.getIDocumentContentOperations());
1709 const auto pType(static_cast<SwDocInfoFieldType*>(
1710 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DocInfo)));
1711 const OUString sDisplayed = GetFieldResult(pF);
1712 SwDocInfoField aField(pType, DI_CUSTOM | nReg, aDocProperty);
1714 // If text already matches the DocProperty var, then safe to treat as refreshable field.
1715 OUString sVariable = aField.ExpandField(/*bCache=*/false, nullptr);
1716 if (sDisplayed.getLength() != sVariable.getLength())
1718 sal_Int32 nLen = sVariable.indexOf('\x0');
1719 if (nLen >= 0)
1720 sVariable = sVariable.copy(0, nLen);
1722 if (sDisplayed == sVariable)
1723 rIDCO.InsertPoolItem(*m_pPaM, SwFormatField(aField));
1724 else
1726 // They don't match, so use a fixed field to prevent LO from altering the contents.
1727 SwDocInfoField aFixedField(pType, DI_CUSTOM | DI_SUB_FIXED | nReg, aDocProperty,
1728 sDisplayed);
1729 rIDCO.InsertPoolItem(*m_pPaM, SwFormatField(aFixedField));
1732 return eF_ResT::OK;
1736 switch( pF->nId )
1738 case 14:
1739 /* supports all INFO variables! */
1740 nSub = DI_KEYS;
1741 break;
1742 case 15:
1743 nSub = DI_TITLE;
1744 break;
1745 case 16:
1746 nSub = DI_SUBJECT;
1747 break;
1748 case 18:
1749 nSub = DI_KEYS;
1750 break;
1751 case 19:
1752 nSub = DI_COMMENT;
1753 break;
1754 case 20:
1755 // MS Word never updates this automatically, so mark as fixed for best compatibility
1756 nSub = DI_CHANGE | DI_SUB_FIXED;
1757 nReg = DI_SUB_AUTHOR;
1758 break;
1759 case 21:
1760 // The real create date can never change, so mark as fixed for best compatibility
1761 nSub = DI_CREATE | DI_SUB_FIXED;
1762 nReg = DI_SUB_DATE;
1763 bDateTime = true;
1764 break;
1765 case 23:
1766 nSub = DI_PRINT | nFldLock;
1767 nReg = DI_SUB_DATE;
1768 bDateTime = true;
1769 break;
1770 case 24:
1771 nSub = DI_DOCNO;
1772 break;
1773 case 22:
1774 nSub = DI_CHANGE | nFldLock;
1775 nReg = DI_SUB_DATE;
1776 bDateTime = true;
1777 break;
1778 case 25:
1779 nSub = DI_CHANGE | nFldLock;
1780 nReg = DI_SUB_TIME;
1781 bDateTime = true;
1782 break;
1783 case 64: // DOCVARIABLE
1784 nSub = DI_CUSTOM;
1785 break;
1788 sal_uInt32 nFormat = 0;
1790 LanguageType nLang(LANGUAGE_SYSTEM);
1791 if (bDateTime)
1793 SvNumFormatType nDT = GetTimeDatePara(rStr, nFormat, nLang, pF->nId);
1794 switch (nDT)
1796 case SvNumFormatType::DATE:
1797 nReg = DI_SUB_DATE;
1798 break;
1799 case SvNumFormatType::TIME:
1800 nReg = DI_SUB_TIME;
1801 break;
1802 case SvNumFormatType::DATETIME:
1803 nReg = DI_SUB_DATE;
1804 break;
1805 default:
1806 nReg = DI_SUB_DATE;
1807 break;
1811 OUString aData;
1812 // Extract DOCVARIABLE varname
1813 if ( 64 == pF->nId )
1815 WW8ReadFieldParams aReadParam( rStr );
1816 for (;;)
1818 const sal_Int32 nRet = aReadParam.SkipToNextToken();
1819 if ( nRet==-1)
1820 break;
1821 switch( nRet )
1823 case -2:
1824 if( aData.isEmpty() )
1825 aData = aReadParam.GetResult();
1826 break;
1827 case '*':
1828 //Skip over MERGEFORMAT
1829 (void)aReadParam.SkipToNextToken();
1830 break;
1834 aData = aData.replaceAll("\"", "");
1837 bool bDone = false;
1838 if (DI_CUSTOM == nSub)
1840 const auto pType(static_cast<SwUserFieldType*>(
1841 m_rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::User, aData, false)));
1842 if (pType)
1844 SwUserField aField(pType, 0, nFormat);
1845 if (bDateTime)
1846 ForceFieldLanguage(aField, nLang);
1847 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
1848 bDone = true;
1851 if (!bDone)
1853 const auto pType(static_cast<SwDocInfoFieldType*>(
1854 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DocInfo)));
1855 SwDocInfoField aField(pType, nSub|nReg, aData, GetFieldResult(pF), nFormat);
1856 if (bDateTime)
1857 ForceFieldLanguage(aField, nLang);
1858 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
1861 return eF_ResT::OK;
1864 eF_ResT SwWW8ImplReader::Read_F_Author(WW8FieldDesc* pF, OUString&)
1866 // SH: The SwAuthorField refers not to the original author but to the current user, better use DocInfo
1867 SwDocInfoField aField( static_cast<SwDocInfoFieldType*>(
1868 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::DocInfo )),
1869 DI_CREATE|DI_SUB_AUTHOR|DI_SUB_FIXED, OUString(), GetFieldResult(pF));
1870 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1871 return eF_ResT::OK;
1874 eF_ResT SwWW8ImplReader::Read_F_TemplName( WW8FieldDesc*, OUString& )
1876 SwTemplNameField aField( static_cast<SwTemplNameFieldType*>(
1877 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::TemplateName )), FF_NAME );
1878 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1879 return eF_ResT::OK;
1882 // Both the date and the time fields can be used for showing a date a time or both.
1883 eF_ResT SwWW8ImplReader::Read_F_DateTime( WW8FieldDesc*pF, OUString& rStr )
1885 bool bHijri = false;
1886 WW8ReadFieldParams aReadParam(rStr);
1887 for (;;)
1889 const sal_Int32 nTok = aReadParam.SkipToNextToken();
1890 if ( nTok==-1 )
1891 break;
1892 switch (nTok)
1894 default:
1895 case 'l':
1896 case -2:
1897 break;
1898 case 'h':
1899 bHijri = true;
1900 break;
1901 case 's':
1902 //Saka Calendar, should we do something with this ?
1903 break;
1907 sal_uInt32 nFormat = 0;
1909 LanguageType nLang(LANGUAGE_SYSTEM);
1910 SvNumFormatType nDT = GetTimeDatePara(rStr, nFormat, nLang, ww::eDATE, bHijri);
1912 if( SvNumFormatType::UNDEFINED == nDT ) // no D/T-Formatstring
1914 if (32 == pF->nId)
1916 nDT = SvNumFormatType::TIME;
1917 nFormat = m_rDoc.GetNumberFormatter()->GetFormatIndex(
1918 NF_TIME_START, LANGUAGE_SYSTEM );
1920 else
1922 nDT = SvNumFormatType::DATE;
1923 nFormat = m_rDoc.GetNumberFormatter()->GetFormatIndex(
1924 NF_DATE_START, LANGUAGE_SYSTEM );
1928 if (nDT & SvNumFormatType::DATE || nDT == SvNumFormatType::TIME)
1930 SwDateTimeField aField(static_cast<SwDateTimeFieldType*>(
1931 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::DateTime)),
1932 nDT & SvNumFormatType::DATE ? DATEFLD : TIMEFLD, nFormat);
1933 if (pF->nOpt & 0x10) // Fixed field
1935 double fSerial;
1936 if (!m_rDoc.GetNumberFormatter()->IsNumberFormat(GetFieldResult(pF), nFormat, fSerial,
1937 SvNumInputOptions::LAX_TIME))
1938 return eF_ResT::TEXT; // just drop the field and insert the plain text.
1939 aField.SetSubType(aField.GetSubType() | FIXEDFLD);
1940 DateTime aSetDateTime(m_rDoc.GetNumberFormatter()->GetNullDate());
1941 aSetDateTime.AddTime(fSerial);
1942 aField.SetDateTime(aSetDateTime);
1944 ForceFieldLanguage(aField, nLang);
1945 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1948 return eF_ResT::OK;
1951 eF_ResT SwWW8ImplReader::Read_F_FileName(WW8FieldDesc*, OUString &rStr)
1953 SwFileNameFormat eType = FF_NAME;
1954 WW8ReadFieldParams aReadParam(rStr);
1955 for (;;)
1957 const sal_Int32 nRet = aReadParam.SkipToNextToken();
1958 if ( nRet==-1 )
1959 break;
1960 switch (nRet)
1962 case 'p':
1963 eType = FF_PATHNAME;
1964 break;
1965 case '*':
1966 //Skip over MERGEFORMAT
1967 (void)aReadParam.SkipToNextToken();
1968 break;
1969 default:
1970 OSL_ENSURE(false, "unknown option in FileName field");
1971 break;
1975 SwFileNameField aField(
1976 static_cast<SwFileNameFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Filename)), eType);
1977 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
1978 return eF_ResT::OK;
1981 eF_ResT SwWW8ImplReader::Read_F_Num( WW8FieldDesc* pF, OUString& rStr )
1983 sal_uInt16 nSub = DS_PAGE; // page number
1984 switch ( pF->nId ){
1985 case 27: nSub = DS_WORD; break; // number of words
1986 case 28: nSub = DS_CHAR; break; // number of characters
1988 SwDocStatField aField( static_cast<SwDocStatFieldType*>(
1989 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::DocStat )), nSub,
1990 GetNumberPara( rStr ) );
1991 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
1992 return eF_ResT::OK;
1995 eF_ResT SwWW8ImplReader::Read_F_CurPage( WW8FieldDesc*, OUString& rStr )
1997 // page number
1998 SwPageNumberField aField( static_cast<SwPageNumberFieldType*>(
1999 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::PageNumber )), PG_RANDOM,
2000 GetNumberPara(rStr, true));
2002 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2003 return eF_ResT::OK;
2006 eF_ResT SwWW8ImplReader::Read_F_Symbol( WW8FieldDesc*, OUString& rStr )
2008 //e.g. #i20118#
2009 OUString aQ;
2010 OUString aName;
2011 sal_Int32 nSize = 0;
2012 WW8ReadFieldParams aReadParam( rStr );
2013 for (;;)
2015 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2016 if ( nRet==-1 )
2017 break;
2018 switch( nRet )
2020 case -2:
2021 if( aQ.isEmpty() )
2022 aQ = aReadParam.GetResult();
2023 break;
2024 case 'f':
2025 case 'F':
2026 if ( aReadParam.GoToTokenParam() )
2027 aName = aReadParam.GetResult();
2028 break;
2029 case 's':
2030 case 'S':
2031 if ( aReadParam.GoToTokenParam() )
2033 const OUString aSiz = aReadParam.GetResult();
2034 if (!aSiz.isEmpty())
2036 bool bFail = o3tl::checked_multiply<sal_Int32>(aSiz.toInt32(), 20, nSize); // pT -> twip
2037 if (bFail)
2038 nSize = -1;
2041 break;
2044 if( aQ.isEmpty() )
2045 return eF_ResT::TAGIGN; // -> no 0-char in text
2047 sal_Unicode const cChar = static_cast<sal_Unicode>(aQ.toInt32());
2048 if (!linguistic::IsControlChar(cChar) || cChar == '\r' || cChar == '\n' || cChar == '\t')
2050 if (!aName.isEmpty()) // Font Name set ?
2052 SvxFontItem aFont(FAMILY_DONTKNOW, aName, OUString(),
2053 PITCH_DONTKNOW, RTL_TEXTENCODING_SYMBOL, RES_CHRATR_FONT);
2054 NewAttr(aFont); // new Font
2057 if (nSize > 0) //#i20118#
2059 SvxFontHeightItem aSz(nSize, 100, RES_CHRATR_FONTSIZE);
2060 NewAttr(aSz);
2063 m_rDoc.getIDocumentContentOperations().InsertString(*m_pPaM, OUString(cChar));
2065 if (nSize > 0)
2066 m_xCtrlStck->SetAttr(*m_pPaM->GetPoint(), RES_CHRATR_FONTSIZE);
2067 if (!aName.isEmpty())
2068 m_xCtrlStck->SetAttr(*m_pPaM->GetPoint(), RES_CHRATR_FONT);
2070 else
2072 m_rDoc.getIDocumentContentOperations().InsertString(*m_pPaM, u"###"_ustr);
2075 return eF_ResT::OK;
2078 // "EMBED"
2079 eF_ResT SwWW8ImplReader::Read_F_Embedd( WW8FieldDesc*, OUString& rStr )
2081 WW8ReadFieldParams aReadParam( rStr );
2082 for (;;)
2084 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2085 if ( nRet==-1 )
2086 break;
2087 switch( nRet )
2089 case -2:
2090 // sHost
2091 break;
2093 case 's':
2094 // use ObjectSize
2095 break;
2099 if( m_bObj && m_nPicLocFc )
2100 m_nObjLocFc = m_nPicLocFc;
2101 m_bEmbeddObj = true;
2102 return eF_ResT::TEXT;
2105 // "SET"
2106 eF_ResT SwWW8ImplReader::Read_F_Set( WW8FieldDesc* pF, OUString& rStr )
2108 OUString sOrigName;
2109 OUString sVal;
2110 WW8ReadFieldParams aReadParam( rStr );
2111 for (;;)
2113 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2114 if ( nRet==-1 )
2115 break;
2116 switch( nRet )
2118 case -2:
2119 if (sOrigName.isEmpty())
2120 sOrigName = aReadParam.GetResult();
2121 else if (sVal.isEmpty())
2122 sVal = aReadParam.GetResult();
2123 break;
2127 const tools::Long nNo = MapBookmarkVariables(pF, sOrigName, sVal);
2129 SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType( SwSetExpFieldType( &m_rDoc, sOrigName,
2130 nsSwGetSetExpType::GSE_STRING ) );
2131 SwSetExpField aField( static_cast<SwSetExpFieldType*>(pFT), sVal, ULONG_MAX );
2132 aField.SetSubType(nsSwExtendedSubType::SUB_INVISIBLE | nsSwGetSetExpType::GSE_STRING);
2134 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2136 m_xReffedStck->SetAttr(*m_pPaM->GetPoint(), RES_FLTR_BOOKMARK, true, nNo);
2138 return eF_ResT::OK;
2141 // "REF"
2142 eF_ResT SwWW8ImplReader::Read_F_Ref( WW8FieldDesc*, OUString& rStr )
2143 { // Reference - Field
2144 OUString sOrigBkmName;
2145 REFERENCEMARK eFormat = REF_CONTENT;
2147 WW8ReadFieldParams aReadParam( rStr );
2148 for (;;)
2150 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2151 if ( nRet==-1 )
2152 break;
2153 switch( nRet )
2155 case -2:
2156 if( sOrigBkmName.isEmpty() ) // get name of bookmark
2157 sOrigBkmName = aReadParam.GetResult();
2158 break;
2160 /* References to numbers in Word could be either to a numbered
2161 paragraph or to a chapter number. However Word does not seem to
2162 have the capability we do, of referring to the chapter number some
2163 other bookmark is in. As a result, cross-references to chapter
2164 numbers in a word document will be cross-references to a numbered
2165 paragraph, being the chapter heading paragraph. As it happens, our
2166 cross-references to numbered paragraphs will do the right thing
2167 when the target is a numbered chapter heading, so there is no need
2168 for us to use the REF_CHAPTER bookmark format on import.
2170 case 'n':
2171 eFormat = REF_NUMBER_NO_CONTEXT;
2172 break;
2173 case 'r':
2174 eFormat = REF_NUMBER;
2175 break;
2176 case 'w':
2177 eFormat = REF_NUMBER_FULL_CONTEXT;
2178 break;
2180 case 'p':
2181 eFormat = REF_UPDOWN;
2182 break;
2183 case 'h':
2184 break;
2185 default:
2186 // unimplemented switch: just do 'nix nought nothing' :-)
2187 break;
2191 OUString sBkmName(GetMappedBookmark(sOrigBkmName));
2193 // #i120879# add cross reference bookmark name prefix, if it
2194 // matches internal TOC bookmark naming convention
2195 if ( IsTOCBookmarkName( sBkmName ) )
2197 sBkmName = EnsureTOCBookmarkName(sBkmName);
2198 // track <sBookmarkName> as referenced TOC bookmark.
2199 m_xReffedStck->m_aReferencedTOCBookmarks.insert( sBkmName );
2202 SwGetRefField aField(
2203 static_cast<SwGetRefFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::GetRef )),
2204 sBkmName,u""_ustr,REF_BOOKMARK,0,0,eFormat);
2206 if (eFormat == REF_CONTENT)
2209 If we are just inserting the contents of the bookmark, then it
2210 is possible that the bookmark is actually a variable, so we
2211 must store it until the end of the document to see if it was,
2212 in which case we'll turn it into a show variable
2214 m_xReffingStck->NewAttr( *m_pPaM->GetPoint(), SwFormatField(aField) );
2215 m_xReffingStck->SetAttr( *m_pPaM->GetPoint(), RES_TXTATR_FIELD);
2217 else
2219 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
2221 return eF_ResT::OK;
2224 // Note Reference - Field
2225 eF_ResT SwWW8ImplReader::Read_F_NoteReference( WW8FieldDesc*, OUString& rStr )
2227 OUString aBkmName;
2228 bool bAboveBelow = false;
2230 WW8ReadFieldParams aReadParam( rStr );
2231 for (;;)
2233 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2234 if ( nRet==-1 )
2235 break;
2236 switch( nRet )
2238 case -2:
2239 if( aBkmName.isEmpty() ) // get name of foot/endnote
2240 aBkmName = aReadParam.GetResult();
2241 break;
2242 case 'r':
2243 // activate flag 'Chapter Number'
2244 break;
2245 case 'p':
2246 bAboveBelow = true;
2247 break;
2248 case 'h':
2249 break;
2250 default:
2251 // unimplemented switch: just do 'nix nought nothing' :-)
2252 break;
2256 // set Sequence No of corresponding Foot-/Endnote to Zero
2257 // (will be corrected in
2258 SwGetRefField aField( static_cast<SwGetRefFieldType*>(
2259 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::GetRef )), aBkmName, u""_ustr, REF_FOOTNOTE, 0, 0,
2260 REF_ONLYNUMBER );
2261 m_xReffingStck->NewAttr(*m_pPaM->GetPoint(), SwFormatField(aField));
2262 m_xReffingStck->SetAttr(*m_pPaM->GetPoint(), RES_TXTATR_FIELD);
2263 if (bAboveBelow)
2265 SwGetRefField aField2( static_cast<SwGetRefFieldType*>(
2266 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::GetRef )),aBkmName, u""_ustr, REF_FOOTNOTE, 0, 0,
2267 REF_UPDOWN );
2268 m_xReffingStck->NewAttr(*m_pPaM->GetPoint(), SwFormatField(aField2));
2269 m_xReffingStck->SetAttr(*m_pPaM->GetPoint(), RES_TXTATR_FIELD);
2271 return eF_ResT::OK;
2274 // "PAGEREF"
2275 eF_ResT SwWW8ImplReader::Read_F_PgRef( WW8FieldDesc*, OUString& rStr )
2277 OUString sOrigName;
2278 WW8ReadFieldParams aReadParam( rStr );
2279 for (;;)
2281 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2282 if ( nRet==-1 )
2283 break;
2284 else if ( nRet == -2 && sOrigName.isEmpty() )
2286 sOrigName = aReadParam.GetResult();
2290 const OUString sName(GetMappedBookmark(sOrigName));
2292 // loading page reference field in TOX
2293 if (m_bLoadingTOXCache)
2295 // insert page ref representation as plain text --> return FLD_TEXT
2296 // if there is no hyperlink settings for current toc and referenced bookmark is available,
2297 // assign link to current ref area
2298 if (!m_bLoadingTOXHyperlink && !sName.isEmpty())
2300 // #i120879# add cross reference bookmark name prefix, if it
2301 // matches internal TOC bookmark naming convention
2302 OUString sBookmarkName;
2303 if ( IsTOCBookmarkName( sName ) )
2305 sBookmarkName = EnsureTOCBookmarkName(sName);
2306 // track <sBookmarkName> as referenced TOC bookmark.
2307 m_xReffedStck->m_aReferencedTOCBookmarks.insert( sBookmarkName );
2309 else
2311 sBookmarkName = sName;
2313 OUString sURL = "#" + sBookmarkName;
2314 SwFormatINetFormat aURL( sURL, u""_ustr );
2315 static constexpr OUString sLinkStyle(u"Index Link"_ustr);
2316 const sal_uInt16 nPoolId =
2317 SwStyleNameMapper::GetPoolIdFromProgName( sLinkStyle, SwGetPoolIdFromName::ChrFmt );
2318 aURL.SetVisitedFormatAndId( sLinkStyle, nPoolId);
2319 aURL.SetINetFormatAndId( sLinkStyle, nPoolId );
2320 m_xCtrlStck->NewAttr( *m_pPaM->GetPoint(), aURL );
2322 return eF_ResT::TEXT;
2325 // #i120879# add cross reference bookmark name prefix, if it matches
2326 // internal TOC bookmark naming convention
2327 OUString sPageRefBookmarkName;
2328 if ( IsTOCBookmarkName( sName ) )
2330 sPageRefBookmarkName = EnsureTOCBookmarkName(sName);
2331 // track <sPageRefBookmarkName> as referenced TOC bookmark.
2332 m_xReffedStck->m_aReferencedTOCBookmarks.insert( sPageRefBookmarkName );
2334 else
2336 sPageRefBookmarkName = sName;
2338 SwGetRefField aField( static_cast<SwGetRefFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::GetRef )),
2339 sPageRefBookmarkName, u""_ustr, REF_BOOKMARK, 0, 0, REF_PAGE );
2340 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2342 return eF_ResT::OK;
2345 //helper function
2346 //For MS MacroButton field, the symbol in plain text is always "(" (0x28),
2347 //which should be mapped according to the macro type
2348 static bool ConvertMacroSymbol( std::u16string_view rName, OUString& rReference )
2350 bool bConverted = false;
2351 if( rReference == "(" )
2353 bConverted = true;
2354 sal_Unicode cSymbol = sal_Unicode(); // silence false warning
2355 if (rName == u"CheckIt")
2356 cSymbol = 0xF06F;
2357 else if (rName == u"UncheckIt")
2358 cSymbol = 0xF0FE;
2359 else if (rName == u"ShowExample")
2360 cSymbol = 0xF02A;
2361 //else if... : todo
2362 else
2363 bConverted = false;
2365 if( bConverted )
2366 rReference = OUString(cSymbol);
2368 return bConverted;
2371 // "MACROBUTTON"
2372 eF_ResT SwWW8ImplReader::Read_F_Macro( WW8FieldDesc*, OUString& rStr)
2374 OUString aName;
2375 OUString aVText;
2376 bool bNewVText = true;
2377 bool bBracket = false;
2378 WW8ReadFieldParams aReadParam( rStr );
2380 for (;;)
2382 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2383 if ( nRet==-1 )
2384 break;
2385 switch( nRet )
2387 case -2:
2388 if( aName.isEmpty() )
2389 aName = aReadParam.GetResult();
2390 else if( aVText.isEmpty() || bBracket )
2392 if( bBracket )
2393 aVText += " ";
2394 aVText += aReadParam.GetResult();
2395 if (bNewVText)
2397 bBracket = (aVText[0] == '[');
2398 bNewVText = false;
2400 else if( aVText.endsWith("]") )
2401 bBracket = false;
2403 break;
2406 if( aName.isEmpty() )
2407 return eF_ResT::TAGIGN; // makes no sense without Macro-Name
2409 NotifyMacroEventRead();
2411 //try converting macro symbol according to macro name
2412 bool bApplyWingdings = ConvertMacroSymbol( aName, aVText );
2413 aName = "StarOffice.Standard.Modul1." + aName;
2415 SwMacroField aField( static_cast<SwMacroFieldType*>(
2416 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Macro )), aName, aVText );
2418 if( !bApplyWingdings )
2419 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2420 else
2422 //set Wingdings font
2423 sal_uInt16 i = 0;
2424 for ( ; i < m_xFonts->GetMax(); i++ )
2426 FontFamily eFamily;
2427 OUString aFontName;
2428 FontPitch ePitch;
2429 rtl_TextEncoding eSrcCharSet;
2430 if( GetFontParams( i, eFamily, aFontName, ePitch, eSrcCharSet )
2431 && aFontName=="Wingdings" )
2433 break;
2437 if ( i < m_xFonts->GetMax() )
2440 SetNewFontAttr( i, true, RES_CHRATR_FONT );
2441 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2442 m_xCtrlStck->SetAttr( *m_pPaM->GetPoint(), RES_CHRATR_FONT );
2443 ResetCharSetVars();
2447 return eF_ResT::OK;
2450 bool CanUseRemoteLink(const OUString &rGrfName)
2452 bool bUseRemote = false;
2455 // Related: tdf#102499, add a default css::ucb::XCommandEnvironment
2456 // in order to have https protocol manage certificates correctly
2457 uno::Reference< task::XInteractionHandler > xIH(
2458 task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr));
2460 uno::Reference< ucb::XProgressHandler > xProgress;
2461 rtl::Reference<::ucbhelper::CommandEnvironment> pCommandEnv =
2462 new ::ucbhelper::CommandEnvironment(new comphelper::SimpleFileAccessInteraction( xIH ), xProgress);
2464 ::ucbhelper::Content aCnt(rGrfName,
2465 static_cast< ucb::XCommandEnvironment* >(pCommandEnv.get()),
2466 comphelper::getProcessComponentContext());
2468 if ( !INetURLObject( rGrfName ).isAnyKnownWebDAVScheme() )
2470 OUString aTitle;
2471 aCnt.getPropertyValue(u"Title"_ustr) >>= aTitle;
2472 bUseRemote = !aTitle.isEmpty();
2474 else
2476 // is a link to a WebDAV resource
2477 // need to use MediaType to check for link usability
2478 OUString aMediaType;
2479 aCnt.getPropertyValue(u"MediaType"_ustr) >>= aMediaType;
2480 bUseRemote = !aMediaType.isEmpty();
2483 catch ( ... )
2485 // this file did not exist, so we will not set this as graphiclink
2486 bUseRemote = false;
2488 return bUseRemote;
2491 // "INCLUDEPICTURE"
2492 eF_ResT SwWW8ImplReader::Read_F_IncludePicture( WW8FieldDesc*, OUString& rStr )
2494 OUString aGrfName;
2495 bool bEmbedded = true;
2497 WW8ReadFieldParams aReadParam( rStr );
2498 for (;;)
2500 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2501 if ( nRet==-1 )
2502 break;
2503 switch( nRet )
2505 case -2:
2506 if (aGrfName.isEmpty())
2507 aGrfName = ConvertFFileName(aReadParam.GetResult());
2508 break;
2510 case 'd':
2511 bEmbedded = false;
2512 break;
2514 case 'c':// skip the converter name
2515 aReadParam.FindNextStringPiece();
2516 break;
2520 if (!bEmbedded)
2521 bEmbedded = !CanUseRemoteLink(aGrfName);
2523 if (!bEmbedded)
2526 Special case:
2528 Now we write the Link into the Doc and remember the SwFlyFrameFormat.
2529 Since we end on return FLD_READ_FSPA below, the skip value will be set
2530 so that Char-1 will still be read.
2531 When we then call SwWW8ImplReader::ImportGraf() it will then recognize
2532 that we have inserted a graphic link and the suiting SwAttrSet will be
2533 inserted into the frame format.
2535 SfxItemSetFixed<RES_FRMATR_BEGIN, RES_FRMATR_END-1> aFlySet( m_rDoc.GetAttrPool() );
2536 aFlySet.Put( SwFormatAnchor( RndStdIds::FLY_AS_CHAR ) );
2537 aFlySet.Put( SwFormatVertOrient( 0, text::VertOrientation::TOP, text::RelOrientation::FRAME ));
2538 m_pFlyFormatOfJustInsertedGraphic =
2539 m_rDoc.getIDocumentContentOperations().InsertGraphic(*m_pPaM,
2540 aGrfName,
2541 OUString(),
2542 nullptr, // Graphic*
2543 &aFlySet,
2544 nullptr, nullptr); // SwFrameFormat*
2545 m_aGrfNameGenerator.SetUniqueGraphName(m_pFlyFormatOfJustInsertedGraphic,
2546 INetURLObject(aGrfName).GetBase());
2548 return eF_ResT::READ_FSPA;
2551 OUString wwSectionNamer::UniqueName()
2553 const OUString aName(msFileLinkSeed + OUString::number(++mnFileSectionNo));
2554 return mrDoc.GetUniqueSectionName(&aName);
2557 // "INCLUDETEXT"
2558 eF_ResT SwWW8ImplReader::Read_F_IncludeText( WW8FieldDesc* /*pF*/, OUString& rStr )
2560 OUString aPara;
2561 OUString aBook;
2562 WW8ReadFieldParams aReadParam( rStr );
2563 for (;;)
2565 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2566 if ( nRet==-1 )
2567 break;
2568 switch( nRet )
2570 case -2:
2571 if( aPara.isEmpty() )
2572 aPara = aReadParam.GetResult();
2573 else if( aBook.isEmpty() )
2574 aBook = aReadParam.GetResult();
2575 break;
2576 case '*':
2577 //Skip over MERGEFORMAT
2578 (void)aReadParam.SkipToNextToken();
2579 break;
2582 aPara = ConvertFFileName(aPara);
2584 if (!aBook.isEmpty() && aBook[ 0 ] != '\\')
2586 // Section from Source (no switch)?
2587 ConvertUFName(aBook);
2588 aPara += OUStringChar(sfx2::cTokenSeparator)
2589 + OUStringChar(sfx2::cTokenSeparator) + aBook;
2593 ##509##
2594 What we will do is insert a section to be linked to a file, but just in
2595 case the file is not available we will fill in the section with the stored
2596 content of this winword field as a fallback.
2598 SwPosition aTmpPos(*m_pPaM->GetPoint());
2600 SwSectionData aSection(SectionType::FileLink,
2601 m_aSectionNameGenerator.UniqueName());
2602 aSection.SetLinkFileName( aPara );
2603 aSection.SetProtectFlag(true);
2605 SwSection *const pSection =
2606 m_rDoc.InsertSwSection(*m_pPaM, aSection, nullptr, nullptr, false);
2607 OSL_ENSURE(pSection, "no section inserted");
2608 if (!pSection)
2609 return eF_ResT::TEXT;
2610 const SwSectionNode* pSectionNode = pSection->GetFormat()->GetSectionNode();
2611 OSL_ENSURE(pSectionNode, "no section node!");
2612 if (!pSectionNode)
2613 return eF_ResT::TEXT;
2615 m_pPaM->GetPoint()->Assign( pSectionNode->GetIndex()+1 );
2617 //we have inserted a section before this point, so adjust pos
2618 //for future page/section segment insertion
2619 m_aSectionManager.PrependedInlineNode(aTmpPos, m_pPaM->GetPointNode());
2621 return eF_ResT::TEXT;
2624 // "SERIALPRINT"
2625 eF_ResT SwWW8ImplReader::Read_F_DBField( WW8FieldDesc* pF, OUString& rStr )
2627 #if !HAVE_FEATURE_DBCONNECTIVITY || ENABLE_FUZZERS
2628 (void) pF;
2629 (void) rStr;
2630 #else
2631 OUString aName;
2632 WW8ReadFieldParams aReadParam( rStr );
2633 for (;;)
2635 const sal_Int32 nRet = aReadParam.SkipToNextToken();
2636 if ( nRet==-1 )
2637 break;
2638 switch( nRet )
2640 case -2:
2641 if( aName.isEmpty() )
2642 aName = aReadParam.GetResult();
2643 break;
2646 SwDBFieldType aD( &m_rDoc, aName, SwDBData() ); // Database: nothing
2648 SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType( aD );
2649 SwDBField aField( static_cast<SwDBFieldType*>(pFT) );
2650 aField.SetFieldCode( rStr );
2652 OUString aResult;
2653 m_xSBase->WW8ReadString( *m_pStrm, aResult, m_xPlcxMan->GetCpOfs()+
2654 pF->nSRes, pF->nLRes, m_eTextCharSet );
2656 aResult = aResult.replace( '\xb', '\n' );
2658 aField.InitContent(aResult);
2660 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField( aField ));
2661 #endif
2662 return eF_ResT::OK;
2665 // "NEXT"
2666 eF_ResT SwWW8ImplReader::Read_F_DBNext( WW8FieldDesc*, OUString& )
2668 #if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
2669 SwDBNextSetFieldType aN;
2670 SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType( aN );
2671 SwDBNextSetField aField( static_cast<SwDBNextSetFieldType*>(pFT), OUString(),
2672 SwDBData() ); // Database: nothing
2673 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2674 #endif
2675 return eF_ResT::OK;
2678 // "DATASET"
2679 eF_ResT SwWW8ImplReader::Read_F_DBNum( WW8FieldDesc*, OUString& )
2681 #if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
2682 SwDBSetNumberFieldType aN;
2683 SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType( aN );
2684 SwDBSetNumberField aField( static_cast<SwDBSetNumberFieldType*>(pFT),
2685 SwDBData() ); // Datenbase: nothing
2686 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) );
2687 #endif
2688 return eF_ResT::OK;
2692 EQ , only the usage for
2693 a. Combined Characters supported, must be exactly in the form that word
2694 only accepts as combined characters, i.e.
2695 eq \o(\s\up Y(XXX),\s\do Y(XXX))
2696 b. Ruby Text supported, must be in the form that word recognizes as being
2697 ruby text
2700 eF_ResT SwWW8ImplReader::Read_F_Equation( WW8FieldDesc*, OUString& rStr )
2702 WW8ReadFieldParams aReadParam( rStr );
2703 const sal_Int32 cChar = aReadParam.SkipToNextToken();
2704 if ('o' == cChar || 'O' == cChar)
2706 EquationResult aResult(ParseCombinedChars(rStr));
2708 if (aResult.sType == "Input")
2710 SwInputField aField( static_cast<SwInputFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Input )),
2711 aResult.sResult, aResult.sResult, INP_TXT, 0 );
2712 m_rDoc.getIDocumentContentOperations().InsertPoolItem( *m_pPaM, SwFormatField( aField ) ); // insert input field
2714 else if (aResult.sType == "CombinedCharacters")
2716 SwCombinedCharField aField(static_cast<SwCombinedCharFieldType*>(
2717 m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::CombinedChars)), aResult.sType);
2718 m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField));
2721 else if ('*' == cChar)
2722 Read_SubF_Ruby(aReadParam);
2724 return eF_ResT::OK;
2727 void SwWW8ImplReader::Read_SubF_Ruby( WW8ReadFieldParams& rReadParam)
2729 sal_uInt16 nJustificationCode=0;
2730 OUString sFontName;
2731 sal_uInt32 nFontSize=0;
2732 OUString sRuby;
2733 OUString sText;
2734 for (;;)
2736 const sal_Int32 nRet = rReadParam.SkipToNextToken();
2737 if ( nRet==-1 )
2738 break;
2739 switch( nRet )
2741 case -2:
2743 OUString sTemp = rReadParam.GetResult();
2744 if( sTemp.startsWithIgnoreAsciiCase( "jc" ) )
2746 sTemp = sTemp.copy(2);
2747 nJustificationCode = o3tl::narrowing<sal_uInt16>(sTemp.toInt32());
2749 else if( sTemp.startsWithIgnoreAsciiCase( "hps" ) )
2751 sTemp = sTemp.copy(3);
2752 nFontSize= static_cast<sal_uInt32>(sTemp.toInt32());
2754 else if( sTemp.startsWithIgnoreAsciiCase( "Font:" ) )
2756 sTemp = sTemp.copy(5);
2757 sFontName = sTemp;
2760 break;
2761 case '*':
2762 break;
2763 case 'o':
2764 for (;;)
2766 const sal_Int32 nRes = rReadParam.SkipToNextToken();
2767 if ( nRes==-1 )
2768 break;
2769 if ('u' == nRes)
2771 if (-2 == rReadParam.SkipToNextToken() &&
2772 rReadParam.GetResult().startsWithIgnoreAsciiCase("p"))
2774 if (-2 == rReadParam.SkipToNextToken())
2776 OUString sPart = rReadParam.GetResult();
2777 sal_Int32 nBegin = sPart.indexOf('(');
2779 //Word disallows brackets in this field,
2780 sal_Int32 nEnd = sPart.indexOf(')');
2782 if ((nBegin != -1) &&
2783 (nEnd != -1) && (nBegin < nEnd))
2785 sRuby = sPart.copy(nBegin+1,nEnd-nBegin-1);
2787 if (-1 != nEnd)
2789 nBegin = sPart.indexOf(',',nEnd);
2790 if (-1 == nBegin)
2792 nBegin = sPart.indexOf(';',nEnd);
2794 nEnd = sPart.lastIndexOf(')');
2796 if ((nBegin != -1) && (nEnd != -1) && (nBegin < nEnd))
2798 sText = sPart.copy(nBegin+1,nEnd-nBegin-1);
2799 sText = sw::FilterControlChars(sText);
2805 break;
2809 //Translate and apply
2810 if (sRuby.isEmpty() || sText.isEmpty() || sFontName.isEmpty() || !nFontSize)
2811 return;
2813 css::text::RubyAdjust eRubyAdjust;
2814 switch (nJustificationCode)
2816 case 0:
2817 eRubyAdjust = css::text::RubyAdjust_CENTER;
2818 break;
2819 case 1:
2820 eRubyAdjust = css::text::RubyAdjust_BLOCK;
2821 break;
2822 case 2:
2823 eRubyAdjust = css::text::RubyAdjust_INDENT_BLOCK;
2824 break;
2825 default:
2826 case 3:
2827 eRubyAdjust = css::text::RubyAdjust_LEFT;
2828 break;
2829 case 4:
2830 eRubyAdjust = css::text::RubyAdjust_RIGHT;
2831 break;
2834 SwFormatRuby aRuby(sRuby);
2835 const SwCharFormat *pCharFormat=nullptr;
2836 //Make a guess at which of asian of western we should be setting
2837 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
2838 sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(sRuby, 0);
2840 //Check to see if we already have a ruby charstyle that this fits
2841 for(const auto& rpCharFormat : m_aRubyCharFormats)
2843 const SvxFontHeightItem &rFH =
2844 rpCharFormat->GetFormatAttr(
2845 GetWhichOfScript(RES_CHRATR_FONTSIZE,nScript));
2846 if (rFH.GetHeight() == nFontSize*10)
2848 const SvxFontItem &rF = rpCharFormat->GetFormatAttr(
2849 GetWhichOfScript(RES_CHRATR_FONT,nScript));
2850 if (rF.GetFamilyName() == sFontName)
2852 pCharFormat = rpCharFormat;
2853 break;
2858 //Create a new char style if necessary
2859 if (!pCharFormat)
2861 OUString aNm;
2862 //Take this as the base name
2863 SwStyleNameMapper::FillUIName(RES_POOLCHR_RUBYTEXT,aNm);
2864 aNm+=OUString::number(m_aRubyCharFormats.size()+1);
2865 SwCharFormat *pFormat = m_rDoc.MakeCharFormat(aNm, m_rDoc.GetDfltCharFormat());
2866 SvxFontHeightItem aHeightItem(nFontSize*10, 100, RES_CHRATR_FONTSIZE);
2867 SvxFontItem aFontItem(FAMILY_DONTKNOW,sFontName,
2868 OUString(), PITCH_DONTKNOW, RTL_TEXTENCODING_DONTKNOW, RES_CHRATR_FONT);
2869 aHeightItem.SetWhich(GetWhichOfScript(RES_CHRATR_FONTSIZE,nScript));
2870 aFontItem.SetWhich(GetWhichOfScript(RES_CHRATR_FONT,nScript));
2871 pFormat->SetFormatAttr(aHeightItem);
2872 pFormat->SetFormatAttr(aFontItem);
2873 m_aRubyCharFormats.push_back(pFormat);
2874 pCharFormat = pFormat;
2877 //Set the charstyle and justification
2878 aRuby.SetCharFormatName(pCharFormat->GetName());
2879 aRuby.SetCharFormatId(pCharFormat->GetPoolFormatId());
2880 aRuby.SetAdjustment(eRubyAdjust);
2882 NewAttr(aRuby);
2883 m_rDoc.getIDocumentContentOperations().InsertString( *m_pPaM, sText );
2884 m_xCtrlStck->SetAttr( *m_pPaM->GetPoint(), RES_TXTATR_CJK_RUBY );
2888 // "table of ..." fields
2890 static void lcl_toxMatchACSwitch(SwDoc const & rDoc,
2891 SwTOXBase& rBase,
2892 WW8ReadFieldParams& rParam,
2893 SwCaptionDisplay eCaptionType)
2895 if ( rParam.GoToTokenParam() )
2897 SwTOXType* pType = const_cast<SwTOXType*>(rDoc.GetTOXType( TOX_ILLUSTRATIONS, 0));
2898 rBase.RegisterToTOXType( *pType );
2899 rBase.SetCaptionDisplay( eCaptionType );
2900 // Read Sequence Name and store in TOXBase
2901 OUString sSeqName( rParam.GetResult() );
2902 lcl_ConvertSequenceName( sSeqName );
2903 rBase.SetSequenceName( sSeqName );
2907 static void EnsureMaxLevelForTemplates(SwTOXBase& rBase)
2909 //If the TOC contains Template entries at levels > the evaluation level
2910 //that was initially taken from the max normal outline level of the word TOC
2911 //then we cannot use that for the evaluation level because writer cuts off
2912 //all styles above that level, while word just cuts off the "standard"
2913 //outline styles, we have no option but to expand to the highest level
2914 //Word included.
2915 if ((rBase.GetLevel() != MAXLEVEL) && (SwTOXElement::Template & rBase.GetCreateType()))
2917 for (sal_uInt16 nI = MAXLEVEL; nI > 0; --nI)
2919 if (!rBase.GetStyleNames(nI-1).isEmpty())
2921 rBase.SetLevel(nI);
2922 break;
2928 static void lcl_toxMatchTSwitch(SwWW8ImplReader const & rReader, SwTOXBase& rBase,
2929 WW8ReadFieldParams& rParam)
2931 if ( !rParam.GoToTokenParam() )
2932 return;
2934 OUString sParams( rParam.GetResult() );
2935 if( sParams.isEmpty() )
2936 return;
2938 sal_Int32 nIndex = 0;
2940 // Delimiters between styles and style levels appears to allow both ; and ,
2942 OUString sTemplate( sParams.getToken(0, ';', nIndex) );
2943 if( -1 == nIndex )
2945 nIndex=0;
2946 sTemplate = sParams.getToken(0, ',', nIndex);
2948 if( -1 == nIndex )
2950 const SwFormat* pStyle = rReader.GetStyleWithOrgWWName(sTemplate);
2951 if( pStyle )
2952 sTemplate = pStyle->GetName();
2953 // Store Style for Level 0 into TOXBase
2954 rBase.SetStyleNames( sTemplate, 0 );
2956 else while( -1 != nIndex )
2958 sal_Int32 nOldIndex=nIndex;
2959 sal_uInt16 nLevel = o3tl::narrowing<sal_uInt16>(
2960 o3tl::toInt32(o3tl::getToken(sParams, 0, ';', nIndex)));
2961 if( -1 == nIndex )
2963 nIndex = nOldIndex;
2964 nLevel = o3tl::narrowing<sal_uInt16>(
2965 o3tl::toInt32(o3tl::getToken(sParams, 0, ',', nIndex)));
2968 if( (0 < nLevel) && (MAXLEVEL >= nLevel) )
2970 nLevel--;
2971 // Store Style and Level into TOXBase
2972 const SwFormat* pStyle
2973 = rReader.GetStyleWithOrgWWName( sTemplate );
2975 if( pStyle )
2976 sTemplate = pStyle->GetName();
2978 OUString sStyles( rBase.GetStyleNames( nLevel ) );
2979 if( !sStyles.isEmpty() )
2980 sStyles += OUStringChar(TOX_STYLE_DELIMITER);
2981 sStyles += sTemplate;
2982 rBase.SetStyleNames( sStyles, nLevel );
2984 // read next style name...
2985 nOldIndex = nIndex;
2986 sTemplate = sParams.getToken(0, ';', nIndex);
2987 if( -1 == nIndex )
2989 nIndex=nOldIndex;
2990 sTemplate = sParams.getToken(0, ',', nIndex);
2995 sal_uInt16 wwSectionManager::CurrentSectionColCount() const
2997 sal_uInt16 nIndexCols = 1;
2998 if (!maSegments.empty())
2999 nIndexCols = maSegments.back().maSep.ccolM1 + 1;
3000 return nIndexCols;
3003 //Will there be a new pagebreak at this position (don't know what type
3004 //until later)
3005 bool wwSectionManager::WillHavePageDescHere(const SwNode& rNd) const
3007 bool bRet = false;
3008 if (!maSegments.empty())
3010 if (!maSegments.back().IsContinuous() &&
3011 maSegments.back().maStart == rNd)
3013 bRet = true;
3016 return bRet;
3019 static sal_uInt16 lcl_GetMaxValidWordTOCLevel(const SwForm &rForm)
3021 // GetFormMax() returns level + 1, hence the -1
3022 sal_uInt16 nRet = rForm.GetFormMax()-1;
3024 // If the max of this type of TOC is greater than the max of a word
3025 // possible toc, then clip to the word max
3026 if (nRet > WW8ListManager::nMaxLevel)
3027 nRet = WW8ListManager::nMaxLevel;
3029 return nRet;
3032 eF_ResT SwWW8ImplReader::Read_F_Tox( WW8FieldDesc* pF, OUString& rStr )
3034 if (!m_bLoadingTOXCache)
3036 m_bLoadingTOXCache = true;
3038 else
3040 // Embedded TOX --> continue reading its content, but no further TOX
3041 // field
3042 ++m_nEmbeddedTOXLevel;
3043 return eF_ResT::TEXT;
3046 if (pF->nLRes < 3)
3047 return eF_ResT::TEXT; // ignore (#i25440#)
3049 TOXTypes eTox; // create a ToxBase
3050 switch( pF->nId )
3052 case 8:
3053 eTox = TOX_INDEX;
3054 break;
3055 case 13:
3056 eTox = TOX_CONTENT;
3057 break;
3058 default:
3059 eTox = TOX_USER;
3060 break;
3063 SwTOXElement nCreateOf = (eTox == TOX_CONTENT) ? SwTOXElement::OutlineLevel : SwTOXElement::Mark;
3065 sal_uInt16 nIndexCols = 1;
3067 const SwTOXType* pType = m_rDoc.GetTOXType( eTox, 0 );
3068 SwForm aOrigForm(eTox);
3069 std::shared_ptr<SwTOXBase> pBase = std::make_shared<SwTOXBase>( pType, aOrigForm, nCreateOf, OUString() );
3070 pBase->SetProtected(m_aSectionManager.CurrentSectionIsProtected());
3071 switch( eTox ){
3072 case TOX_INDEX:
3074 SwTOIOptions eOptions = SwTOIOptions::SameEntry | SwTOIOptions::CaseSensitive;
3076 // We set SwTOXElement::OutlineLevel only if
3077 // the parameter \o is within 1 to 9
3078 // or the parameter \f exists
3079 // or NO switch parameter are given at all.
3080 WW8ReadFieldParams aReadParam( rStr );
3081 for (;;)
3083 const sal_Int32 nRet = aReadParam.SkipToNextToken();
3084 if ( nRet==-1 )
3085 break;
3086 switch( nRet )
3088 case 'c':
3089 if ( aReadParam.GoToTokenParam() )
3091 const OUString sParams( aReadParam.GetResult() );
3092 // if NO OUString just ignore the \c
3093 if( !sParams.isEmpty() )
3095 nIndexCols = o3tl::narrowing<sal_uInt16>(sParams.toInt32());
3098 break;
3099 case 'e':
3101 if ( aReadParam.GoToTokenParam() ) // if NO String just ignore the \e
3103 OUString sDelimiter( aReadParam.GetResult() );
3104 SwForm aForm( pBase->GetTOXForm() );
3106 // Attention: if TOX_CONTENT brave
3107 // GetFormMax() returns MAXLEVEL + 1 !!
3108 sal_uInt16 nEnd = aForm.GetFormMax()-1;
3110 for(sal_uInt16 nLevel = 1;
3111 nLevel <= nEnd;
3112 ++nLevel)
3114 // Levels count from 1
3115 // Level 0 is reserved for CAPTION
3117 // Insert delimiter instead of tab in front of the page number if there is one:
3118 FormTokenType ePrevType = TOKEN_END;
3119 FormTokenType eType;
3120 // -> #i21237#
3121 SwFormTokens aPattern =
3122 aForm.GetPattern(nLevel);
3123 SwFormTokens::iterator aIt = aPattern.begin();
3126 eType = ++aIt == aPattern.end() ? TOKEN_END : aIt->eTokenType;
3128 if (eType == TOKEN_PAGE_NUMS)
3130 if (TOKEN_TAB_STOP == ePrevType)
3132 --aIt;
3134 if (!sDelimiter.isEmpty() && sDelimiter[0] == 0x09)
3135 aIt->eTabAlign = SvxTabAdjust::End;
3136 else
3138 SwFormToken aToken(TOKEN_TEXT);
3139 aToken.sText = sDelimiter;
3140 *aIt = std::move(aToken);
3142 aForm.SetPattern(nLevel, std::move(aPattern));
3145 eType = TOKEN_END;
3148 ePrevType = eType;
3150 while (TOKEN_END != eType);
3151 // <- #i21237#
3153 pBase->SetTOXForm( aForm );
3156 break;
3157 case 'h':
3159 eOptions |= SwTOIOptions::AlphaDelimiter;
3161 break;
3164 pBase->SetOptions( eOptions );
3166 break;
3168 case TOX_CONTENT:
3170 bool bIsHyperlink = false;
3171 // We set SwTOXElement::OutlineLevel only if
3172 // the parameter \o is within 1 to 9
3173 // or the parameter \f exists
3174 // or NO switch parameter are given at all.
3175 SwTOXElement eCreateFrom = SwTOXElement::NONE;
3176 sal_Int32 nMaxLevel = 0;
3177 WW8ReadFieldParams aReadParam( rStr );
3178 for (;;)
3180 const sal_Int32 nRet = aReadParam.SkipToNextToken();
3181 if ( nRet==-1 )
3182 break;
3183 switch( nRet )
3185 case 'h':
3186 bIsHyperlink = true;
3187 break;
3188 case 'a':
3189 case 'c':
3190 lcl_toxMatchACSwitch(m_rDoc, *pBase, aReadParam,
3191 ('c' == nRet)
3192 ? CAPTION_COMPLETE
3193 : CAPTION_TEXT );
3194 break;
3195 case 'o':
3197 sal_Int32 nVal;
3198 if( !aReadParam.GetTokenSttFromTo(nullptr, &nVal, WW8ListManager::nMaxLevel) )
3199 nVal = lcl_GetMaxValidWordTOCLevel(aOrigForm);
3200 if( nMaxLevel < nVal )
3201 nMaxLevel = nVal;
3202 eCreateFrom |= SwTOXElement::OutlineLevel;
3204 break;
3205 case 'f':
3206 eCreateFrom |= SwTOXElement::Mark;
3207 break;
3208 case 'l':
3210 sal_Int32 nVal;
3211 if( aReadParam.GetTokenSttFromTo(nullptr, &nVal, WW8ListManager::nMaxLevel) )
3213 if( nMaxLevel < nVal )
3214 nMaxLevel = nVal;
3215 eCreateFrom |= SwTOXElement::Mark;
3218 break;
3219 case 't': // paragraphs using special styles shall
3220 // provide the TOX's content
3221 lcl_toxMatchTSwitch(*this, *pBase, aReadParam);
3222 eCreateFrom |= SwTOXElement::Template;
3223 break;
3224 case 'p':
3226 if ( aReadParam.GoToTokenParam() ) // if NO String just ignore the \p
3228 OUString sDelimiter( aReadParam.GetResult() );
3229 SwForm aForm( pBase->GetTOXForm() );
3231 // Attention: if TOX_CONTENT brave
3232 // GetFormMax() returns MAXLEVEL + 1 !!
3233 sal_uInt16 nEnd = aForm.GetFormMax()-1;
3235 for(sal_uInt16 nLevel = 1;
3236 nLevel <= nEnd;
3237 ++nLevel)
3239 // Levels count from 1
3240 // Level 0 is reserved for CAPTION
3242 // Insert delimiter instead of tab in front of the pagenumber if there is one:
3243 FormTokenType ePrevType = TOKEN_END;
3244 FormTokenType eType;
3246 // -> #i21237#
3247 SwFormTokens aPattern = aForm.GetPattern(nLevel);
3248 SwFormTokens::iterator aIt = aPattern.begin();
3251 eType = ++aIt == aPattern.end() ? TOKEN_END : aIt->eTokenType;
3253 if (eType == TOKEN_PAGE_NUMS)
3255 if (TOKEN_TAB_STOP == ePrevType)
3257 --aIt;
3259 SwFormToken aToken(TOKEN_TEXT);
3260 aToken.sText = sDelimiter;
3262 *aIt = std::move(aToken);
3263 aForm.SetPattern(nLevel,
3264 std::move(aPattern));
3266 eType = TOKEN_END;
3268 ePrevType = eType;
3270 while( TOKEN_END != eType );
3271 // <- #i21237#
3273 pBase->SetTOXForm( aForm );
3276 break;
3277 case 'n': // don't print page numbers
3279 // read START and END param
3280 sal_Int32 nStart(0);
3281 sal_Int32 nEnd(0);
3282 if( !aReadParam.GetTokenSttFromTo( &nStart, &nEnd,
3283 WW8ListManager::nMaxLevel ) )
3285 nStart = 1;
3286 nEnd = aOrigForm.GetFormMax()-1;
3288 // remove page numbers from this levels
3289 SwForm aForm( pBase->GetTOXForm() );
3290 if (aForm.GetFormMax() <= nEnd)
3291 nEnd = aForm.GetFormMax()-1;
3292 for ( sal_Int32 nLevel = nStart; nLevel<=nEnd; ++nLevel )
3294 // Levels count from 1
3295 // Level 0 is reserved for CAPTION
3297 // Remove pagenumber and if necessary the tab in front of it:
3298 FormTokenType eType;
3299 // -> #i21237#
3300 SwFormTokens aPattern = aForm.GetPattern(nLevel);
3301 SwFormTokens::iterator aIt = aPattern.begin();
3304 eType = ++aIt == aPattern.end() ? TOKEN_END : aIt->eTokenType;
3306 if (eType == TOKEN_PAGE_NUMS)
3308 aIt = aPattern.erase(aIt);
3309 --aIt;
3310 if (
3311 TOKEN_TAB_STOP ==
3312 aIt->eTokenType
3315 aPattern.erase(aIt);
3316 aForm.SetPattern(nLevel, std::move(aPattern));
3318 eType = TOKEN_END;
3321 while (TOKEN_END != eType);
3322 // <- #i21237#
3324 pBase->SetTOXForm( aForm );
3326 break;
3329 // the following switches are not (yet) supported
3330 // by good old StarWriter:
3331 case 'b':
3332 case 's':
3333 case 'd':
3334 break;
3339 // For loading the expression of TOC field, we need to mapping its parameters to TOX entries tokens
3340 // also include the hyperlinks and page references
3341 SwFormToken aLinkStart(TOKEN_LINK_START);
3342 SwFormToken aLinkEnd(TOKEN_LINK_END);
3343 aLinkStart.sCharStyleName = "Index Link";
3344 aLinkEnd.sCharStyleName = "Index Link";
3345 SwForm aForm(pBase->GetTOXForm());
3346 sal_uInt16 nEnd = aForm.GetFormMax()-1;
3348 for(sal_uInt16 nLevel = 1; nLevel <= nEnd; ++nLevel)
3350 SwFormTokens aPattern = aForm.GetPattern(nLevel);
3351 if ( bIsHyperlink )
3353 aPattern.insert(aPattern.begin(), aLinkStart);
3355 else
3357 auto aItr = std::find_if(aPattern.begin(), aPattern.end(),
3358 [](const SwFormToken& rToken) { return rToken.eTokenType == TOKEN_PAGE_NUMS; });
3359 if (aItr != aPattern.end())
3360 aPattern.insert(aItr, aLinkStart);
3362 aPattern.push_back(aLinkEnd);
3363 aForm.SetPattern(nLevel, std::move(aPattern));
3365 pBase->SetTOXForm(aForm);
3367 if (!nMaxLevel)
3368 nMaxLevel = WW8ListManager::nMaxLevel;
3369 pBase->SetLevel(nMaxLevel);
3371 const TOXTypes eType = pBase->GetTOXType()->GetType();
3372 switch( eType )
3374 case TOX_CONTENT:
3376 //If we would be created from outlines, either explicitly or by default
3377 //then see if we need extra styles added to the outlines
3378 SwTOXElement eEffectivelyFrom = eCreateFrom != SwTOXElement::NONE ? eCreateFrom : SwTOXElement::OutlineLevel;
3379 if (eEffectivelyFrom & SwTOXElement::OutlineLevel)
3381 // #i19683# Insert a text token " " between the number and entry token.
3382 // In an ideal world we could handle the tab stop between the number and
3383 // the entry correctly, but I currently have no clue how to obtain
3384 // the tab stop position. It is _not_ set at the paragraph style.
3385 std::unique_ptr<SwForm> pForm;
3386 for (const SwWW8StyInf & rSI : m_vColl)
3388 if (rSI.IsOutlineNumbered())
3390 sal_uInt16 nStyleLevel = rSI.mnWW8OutlineLevel;
3391 const SwNumFormat& rFormat = rSI.GetOutlineNumrule()->Get( nStyleLevel );
3392 if ( SVX_NUM_NUMBER_NONE != rFormat.GetNumberingType() )
3394 ++nStyleLevel;
3396 if ( !pForm )
3397 pForm.reset(new SwForm( pBase->GetTOXForm() ));
3399 SwFormTokens aPattern = pForm->GetPattern(nStyleLevel);
3400 SwFormTokens::iterator aIt =
3401 find_if(aPattern.begin(), aPattern.end(),
3402 SwFormTokenEqualToFormTokenType(TOKEN_ENTRY_NO));
3404 if ( aIt != aPattern.end() )
3406 SwFormToken aNumberEntrySeparator( TOKEN_TEXT );
3407 aNumberEntrySeparator.sText = " ";
3408 aPattern.insert( ++aIt, aNumberEntrySeparator );
3409 pForm->SetPattern( nStyleLevel, std::move(aPattern) );
3414 if ( pForm )
3416 pBase->SetTOXForm( *pForm );
3420 if (eCreateFrom != SwTOXElement::NONE)
3421 pBase->SetCreate(eCreateFrom);
3422 EnsureMaxLevelForTemplates(*pBase);
3424 break;
3425 case TOX_ILLUSTRATIONS:
3427 if( eCreateFrom == SwTOXElement::NONE )
3428 eCreateFrom = SwTOXElement::Sequence;
3429 pBase->SetCreate( eCreateFrom );
3432 We don't know until here if we are an illustration
3433 or not, and so have being used a TOX_CONTENT so far
3434 which has 10 levels, while TOX has only two, this
3435 level is set only in the constructor of SwForm, so
3436 create a new one and copy over anything that could
3437 be set in the old one, and remove entries from the
3438 pattern which do not apply to illustration indices
3440 SwForm aOldForm( pBase->GetTOXForm() );
3441 SwForm aNewForm( eType );
3442 sal_uInt16 nNewEnd = aNewForm.GetFormMax()-1;
3444 // #i21237#
3445 for(sal_uInt16 nLevel = 1; nLevel <= nNewEnd; ++nLevel)
3447 SwFormTokens aPattern = aOldForm.GetPattern(nLevel);
3448 SwFormTokens::iterator new_end =
3449 remove_if(aPattern.begin(), aPattern.end(), SwFormTokenEqualToFormTokenType(TOKEN_ENTRY_NO));
3450 aPattern.erase(new_end, aPattern.end() ); // table index imported with wrong page number format
3451 aForm.SetPattern( nLevel, std::move(aPattern) );
3452 aForm.SetTemplate( nLevel, aOldForm.GetTemplate(nLevel) );
3455 pBase->SetTOXForm( aNewForm );
3457 break;
3458 default:
3459 OSL_ENSURE(false, "Unhandled toc options!");
3460 break;
3463 break;
3464 case TOX_USER:
3465 break;
3466 default:
3467 OSL_ENSURE(false, "Unhandled toc options!");
3468 break;
3469 } // ToxBase fertig
3471 // #i21237# - propagate tab stops from paragraph styles used in TOX to patterns of the TOX
3472 pBase->AdjustTabStops( m_rDoc );
3474 //#i10028# inserting a toc implicitly acts like a parabreak in word and writer
3475 if ( m_pPaM->End() &&
3476 m_pPaM->End()->GetNode().GetTextNode() &&
3477 m_pPaM->End()->GetNode().GetTextNode()->Len() != 0 )
3479 m_bCareFirstParaEndInToc = true;
3482 if (m_pPaM->GetPoint()->GetContentIndex())
3483 FinalizeTextNode(*m_pPaM->GetPoint());
3485 const SwPosition* pPos = m_pPaM->GetPoint();
3487 SwFltTOX aFltTOX(std::move(pBase));
3489 // test if there is already a break item on this node
3490 if(SwContentNode* pNd = pPos->GetNode().GetContentNode())
3492 const SfxItemSet* pSet = pNd->GetpSwAttrSet();
3493 if( pSet )
3495 if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false))
3496 aFltTOX.SetHadBreakItem(true);
3497 if (SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false))
3498 aFltTOX.SetHadPageDescItem(true);
3502 //Will there be a new pagebreak at this position (don't know what type
3503 //until later)
3504 if (m_aSectionManager.WillHavePageDescHere(pPos->GetNode()))
3505 aFltTOX.SetHadPageDescItem(true);
3507 // Set start in stack
3508 m_xReffedStck->NewAttr( *pPos, aFltTOX );
3510 m_rDoc.InsertTableOf(*m_pPaM->GetPoint(), aFltTOX.GetBase());
3512 //The TOC field representation contents should be inserted into TOC section, but not after TOC section.
3513 //So we need update the document position when loading TOC representation and after loading TOC;
3514 m_oPosAfterTOC.emplace(*m_pPaM, m_pPaM);
3515 (*m_pPaM).Move(fnMoveBackward);
3516 SwPaM aRegion(*m_pPaM, m_pPaM);
3518 OSL_ENSURE(SwDoc::GetCurTOX(*aRegion.GetPoint()), "Misunderstood how toc works");
3519 if (SwTOXBase* pBase2 = SwDoc::GetCurTOX(*aRegion.GetPoint()))
3521 pBase2->SetMSTOCExpression(rStr);
3523 if ( nIndexCols > 1 )
3525 // Set the column number for index
3526 SfxItemSetFixed<RES_COL, RES_COL> aSet( m_rDoc.GetAttrPool() );
3527 SwFormatCol aCol;
3528 aCol.Init( nIndexCols, 708, USHRT_MAX );
3529 aSet.Put( aCol );
3530 pBase2->SetAttrSet( aSet );
3533 // inserting a toc inserts a section before this point, so adjust pos
3534 // for future page/section segment insertion
3535 m_aSectionManager.PrependedInlineNode( *m_oPosAfterTOC->GetPoint(), aRegion.GetPointNode() );
3538 // Set end in stack
3539 m_xReffedStck->SetAttr( *pPos, RES_FLTR_TOX );
3541 if (!m_aApos.back()) //a para end in apo doesn't count
3542 m_bWasParaEnd = true;
3544 //Return FLD_TEXT, instead of FLD_OK
3545 //FLD_TEXT means the following content, commonly indicate the field representation content should be parsed
3546 //FLD_OK means the current field loading is finished. The rest part should be ignored.
3547 return eF_ResT::TEXT;
3550 eF_ResT SwWW8ImplReader::Read_F_Shape(WW8FieldDesc* /*pF*/, OUString& /*rStr*/)
3553 #i3958# 0x8 followed by 0x1 where the shape is the 0x8 and its anchoring
3554 to be ignored followed by a 0x1 with an empty drawing. Detect in inserting
3555 the drawing that we are in the Shape field and respond accordingly
3557 return eF_ResT::TEXT;
3560 eF_ResT SwWW8ImplReader::Read_F_Hyperlink( WW8FieldDesc* /*pF*/, OUString& rStr )
3562 OUString sURL, sTarget, sMark;
3564 //HYPERLINK "filename" [switches]
3565 rStr = comphelper::string::stripEnd(rStr, 1);
3567 bool bOptions = false;
3568 WW8ReadFieldParams aReadParam( rStr );
3569 for (;;)
3571 const sal_Int32 nRet = aReadParam.SkipToNextToken();
3572 if ( nRet==-1 )
3573 break;
3574 switch( nRet )
3576 case -2:
3577 if (sURL.isEmpty() && !bOptions)
3578 sURL = ConvertFFileName(aReadParam.GetResult());
3579 break;
3581 case 'n':
3582 sTarget = "_blank";
3583 bOptions = true;
3584 break;
3586 case 'l':
3587 bOptions = true;
3588 if ( aReadParam.SkipToNextToken()==-2 )
3590 sMark = aReadParam.GetResult();
3591 if( sMark.endsWith("\""))
3593 sMark = sMark.copy( 0, sMark.getLength() - 1 );
3595 // #120879# add cross reference bookmark name prefix, if it matches internal TOC bookmark naming convention
3596 if ( IsTOCBookmarkName( sMark ) )
3598 sMark = EnsureTOCBookmarkName(sMark);
3599 // track <sMark> as referenced TOC bookmark.
3600 m_xReffedStck->m_aReferencedTOCBookmarks.insert( sMark );
3603 if (m_bLoadingTOXCache)
3605 m_bLoadingTOXHyperlink = true; //on loading a TOC field nested hyperlink field
3608 break;
3609 case 't':
3610 bOptions = true;
3611 if ( aReadParam.SkipToNextToken()==-2 )
3612 sTarget = aReadParam.GetResult();
3613 break;
3614 case 'h':
3615 case 'm':
3616 OSL_ENSURE( false, "Analysis still missing - unknown data" );
3617 [[fallthrough]];
3618 case 's': //worthless fake anchor option
3619 bOptions = true;
3620 break;
3624 // use the result
3625 OSL_ENSURE(!sURL.isEmpty() || !sMark.isEmpty(), "WW8: Empty URL");
3627 if( !sMark.isEmpty() )
3628 sURL += "#" + sMark;
3630 SwFormatINetFormat aURL(sURL, sTarget);
3631 // If on loading TOC field, change the default style into the "index link"
3632 if (m_bLoadingTOXCache)
3634 OUString sLinkStyle(u"Index Link"_ustr);
3635 sal_uInt16 nPoolId =
3636 SwStyleNameMapper::GetPoolIdFromProgName( sLinkStyle, SwGetPoolIdFromName::ChrFmt );
3637 aURL.SetVisitedFormatAndId( sLinkStyle, nPoolId );
3638 aURL.SetINetFormatAndId( sLinkStyle, nPoolId );
3641 //As an attribute this needs to be closed, and that'll happen from
3642 //EndExtSprm in conjunction with the maFieldStack. If there are flyfrms
3643 //between the start and begin, their hyperlinks will be set at that time
3644 //as well.
3645 m_xCtrlStck->NewAttr( *m_pPaM->GetPoint(), aURL );
3646 return eF_ResT::TEXT;
3649 static void lcl_ImportTox(SwDoc &rDoc, SwPaM const &rPaM, const OUString &rStr, bool bIdx)
3651 TOXTypes eTox = ( !bIdx ) ? TOX_CONTENT : TOX_INDEX; // Default
3653 sal_uInt16 nLevel = 1;
3655 OUString sFieldText;
3656 WW8ReadFieldParams aReadParam(rStr);
3657 for (;;)
3659 const sal_Int32 nRet = aReadParam.SkipToNextToken();
3660 if ( nRet==-1 )
3661 break;
3662 switch( nRet )
3664 case -2:
3665 if( sFieldText.isEmpty() )
3667 // PrimaryKey without ":", 2nd after
3668 sFieldText = aReadParam.GetResult();
3670 break;
3672 case 'f':
3673 if ( aReadParam.GoToTokenParam() )
3675 const OUString sParams( aReadParam.GetResult() );
3676 if( sParams[0]!='C' && sParams[0]!='c' )
3677 eTox = TOX_USER;
3679 break;
3681 case 'l':
3682 if ( aReadParam.GoToTokenParam() )
3684 const OUString sParams( aReadParam.GetResult() );
3685 // if NO String just ignore the \l
3686 if( !sParams.isEmpty() && sParams[0]>'0' && sParams[0]<='9' )
3688 nLevel = o3tl::narrowing<sal_uInt16>(sParams.toInt32());
3691 break;
3695 OSL_ENSURE( rDoc.GetTOXTypeCount( eTox ), "Doc.GetTOXTypeCount() == 0 :-(" );
3697 const SwTOXType* pT = rDoc.GetTOXType( eTox, 0 );
3698 SwTOXMark aM( pT );
3700 if( eTox != TOX_INDEX )
3701 aM.SetLevel( nLevel );
3702 else
3704 sal_Int32 nFnd = sFieldText.indexOf( WW8_TOX_LEVEL_DELIM );
3705 if( -1 != nFnd ) // it exist levels
3707 aM.SetPrimaryKey( sFieldText.copy( 0, nFnd ) );
3708 sal_Int32 nScndFnd = sFieldText.indexOf( WW8_TOX_LEVEL_DELIM, nFnd+1 );
3709 if( -1 != nScndFnd )
3711 aM.SetSecondaryKey( sFieldText.copy( nFnd+1, nScndFnd - nFnd - 1 ));
3712 nFnd = nScndFnd;
3714 sFieldText = sFieldText.copy( nFnd+1 );
3718 if (!sFieldText.isEmpty())
3720 aM.SetAlternativeText( sFieldText );
3721 rDoc.getIDocumentContentOperations().InsertPoolItem( rPaM, aM );
3725 void SwWW8ImplReader::ImportTox( int nFieldId, const OUString& aStr )
3727 bool bIdx = (nFieldId != 9);
3728 lcl_ImportTox(m_rDoc, *m_pPaM, aStr, bIdx);
3731 void SwWW8ImplReader::Read_FieldVanish( sal_uInt16, const sal_uInt8*, short nLen )
3733 //Meaningless in a style
3734 if (m_pCurrentColl || !m_xPlcxMan)
3735 return;
3737 const int nChunk = 64; //number of characters to read at one time
3739 // Careful: MEMICMP doesn't work with fieldnames including umlauts!
3740 const static char *aFieldNames[] = { "\x06""INHALT", "\x02""XE", // dt.
3741 "\x02""TC" }; // us
3742 const static sal_uInt8 aFieldId[] = { 9, 4, 9 };
3744 if( nLen < 0 )
3746 m_bIgnoreText = false;
3747 return;
3750 // our method was called from
3751 // ''Skip attributes of field contents'' loop within ReadTextAttr()
3752 if( m_bIgnoreText )
3753 return;
3755 m_bIgnoreText = true;
3756 sal_uInt64 nOldPos = m_pStrm->Tell();
3758 WW8_CP nStartCp = m_xPlcxMan->Where() + m_xPlcxMan->GetCpOfs();
3760 OUString sFieldName;
3761 sal_Int32 nFieldLen = m_xSBase->WW8ReadString( *m_pStrm, sFieldName, nStartCp,
3762 nChunk, m_eStructCharSet );
3763 nStartCp+=nFieldLen;
3765 sal_Int32 nC = 0;
3766 //If the first chunk did not start with a field start then
3767 //reset the stream position and give up
3768 if( !nFieldLen || sFieldName[nC]!=0x13 ) // Field Start Mark
3770 // If Field End Mark found
3771 if( nFieldLen && sFieldName[nC]==0x15 )
3772 m_bIgnoreText = false;
3773 m_pStrm->Seek( nOldPos );
3774 return; // no field found
3777 sal_Int32 nFnd;
3778 //If this chunk does not contain a field end, keep reading chunks
3779 //until we find one, or we run out of text,
3780 for (;;)
3782 nFnd = sFieldName.indexOf(0x15);
3783 //found field end, we can stop now
3784 if (nFnd != -1)
3785 break;
3786 OUString sTemp;
3787 nFieldLen = m_xSBase->WW8ReadString( *m_pStrm, sTemp,
3788 nStartCp, nChunk, m_eStructCharSet );
3789 sFieldName+=sTemp;
3790 nStartCp+=nFieldLen;
3791 if (!nFieldLen)
3792 break;
3795 m_pStrm->Seek( nOldPos );
3797 //if we have no 0x15 give up, otherwise erase everything from the 0x15
3798 //onwards
3799 if (nFnd<0)
3800 return;
3802 sFieldName = sFieldName.copy(0, nFnd);
3804 nC++;
3805 while ( sFieldName[nC]==' ' )
3806 nC++;
3808 for( int i = 0; i < 3; i++ )
3810 const char* pName = aFieldNames[i];
3811 const sal_Int32 nNameLen = static_cast<sal_Int32>(*pName++);
3812 if( sFieldName.matchIgnoreAsciiCaseAsciiL( pName, nNameLen, nC ) )
3814 ImportTox( aFieldId[i], sFieldName.copy( nC + nNameLen ) );
3815 break; // no duplicates allowed
3818 m_bIgnoreText = true;
3819 m_pStrm->Seek( nOldPos );
3822 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */