1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <sal/config.h>
12 #include <string_view>
14 #include "rtfdocumentimpl.hxx"
16 #include <com/sun/star/document/DocumentProperties.hpp>
17 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
18 #include <com/sun/star/text/VertOrientation.hpp>
19 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
21 #include <filter/msfilter/escherex.hxx>
22 #include <rtl/character.hxx>
23 #include <tools/stream.hxx>
24 #include <sal/log.hxx>
26 #include <dmapper/DomainMapperFactory.hxx>
27 #include <ooxml/resourceids.hxx>
29 #include "rtflookahead.hxx"
30 #include "rtfreferenceproperties.hxx"
31 #include "rtfsdrimport.hxx"
32 #include "rtfskipdestination.hxx"
33 #include "rtftokenizer.hxx"
35 using namespace com::sun::star
;
37 namespace writerfilter::rtftok
39 RTFError
RTFDocumentImpl::dispatchDestination(RTFKeyword nKeyword
)
42 checkUnicode(/*bUnicode =*/true, /*bHex =*/true);
43 RTFSkipDestination
aSkip(*this);
44 // special case \upr: ignore everything except nested \ud
45 if (Destination::UPR
== m_aStates
.top().getDestination() && RTFKeyword::UD
!= nKeyword
)
47 m_aStates
.top().setDestination(Destination::SKIP
);
48 aSkip
.setParsed(false);
55 case RTFKeyword::FONTTBL
:
56 m_aStates
.top().setDestination(Destination::FONTTABLE
);
58 case RTFKeyword::COLORTBL
:
59 m_aStates
.top().setDestination(Destination::COLORTABLE
);
61 case RTFKeyword::STYLESHEET
:
62 m_aStates
.top().setDestination(Destination::STYLESHEET
);
64 case RTFKeyword::FIELD
:
65 m_aStates
.top().setDestination(Destination::FIELD
);
66 m_aStates
.top().setFieldLocked(false);
68 case RTFKeyword::DOCVAR
:
69 m_aStates
.top().setDestination(Destination::DOCVAR
);
71 case RTFKeyword::FLDINST
:
73 // Look for the field type
74 sal_uInt64
const nPos
= Strm().Tell();
77 bool bFoundCode
= false;
78 bool bInKeyword
= false;
79 while (!bFoundCode
&& ch
!= '}')
84 if (!bInKeyword
&& rtl::isAsciiAlphanumeric(static_cast<unsigned char>(ch
)))
86 else if (bInKeyword
&& rtl::isAsciiWhiteSpace(static_cast<unsigned char>(ch
)))
89 && !rtl::isAsciiAlphanumeric(static_cast<unsigned char>(ch
)))
93 if (std::string_view(aBuf
) == "INCLUDEPICTURE")
95 // Extract the field argument of INCLUDEPICTURE: we handle that
96 // at a tokenizer level, as DOCX has no such field.
105 OUString aFieldCommand
= OStringToOUString(aBuf
, RTL_TEXTENCODING_UTF8
);
106 std::tuple
<OUString
, std::vector
<OUString
>, std::vector
<OUString
>> aResult
107 = writerfilter::dmapper::splitFieldCommand(aFieldCommand
);
109 = std::get
<1>(aResult
).empty() ? OUString() : std::get
<1>(aResult
).front();
114 // Form data should be handled only for form fields if any
115 if (aBuf
.toString().indexOf("FORM") != -1)
118 singleChar(cFieldStart
);
119 m_aStates
.top().setDestination(Destination::FIELDINSTRUCTION
);
122 case RTFKeyword::FLDRSLT
:
123 m_aStates
.top().setDestination(Destination::FIELDRESULT
);
125 case RTFKeyword::LISTTABLE
:
126 m_aStates
.top().setDestination(Destination::LISTTABLE
);
128 case RTFKeyword::LISTPICTURE
:
129 m_aStates
.top().setDestination(Destination::LISTPICTURE
);
130 m_aStates
.top().setInListpicture(true);
132 case RTFKeyword::LIST
:
133 m_aStates
.top().setDestination(Destination::LISTENTRY
);
135 case RTFKeyword::LISTNAME
:
136 m_aStates
.top().setDestination(Destination::LISTNAME
);
138 case RTFKeyword::LFOLEVEL
:
139 m_aStates
.top().setDestination(Destination::LFOLEVEL
);
140 m_aStates
.top().getTableSprms().clear();
142 case RTFKeyword::LISTOVERRIDETABLE
:
143 m_aStates
.top().setDestination(Destination::LISTOVERRIDETABLE
);
145 case RTFKeyword::LISTOVERRIDE
:
146 m_aStates
.top().setDestination(Destination::LISTOVERRIDEENTRY
);
148 case RTFKeyword::LISTLEVEL
:
149 m_aStates
.top().setDestination(Destination::LISTLEVEL
);
152 case RTFKeyword::LEVELTEXT
:
153 m_aStates
.top().setDestination(Destination::LEVELTEXT
);
155 case RTFKeyword::LEVELNUMBERS
:
156 m_aStates
.top().setDestination(Destination::LEVELNUMBERS
);
158 case RTFKeyword::SHPPICT
:
160 m_aStates
.top().setDestination(Destination::SHPPICT
);
162 case RTFKeyword::PICT
:
163 if (m_aStates
.top().getDestination() != Destination::SHAPEPROPERTYVALUE
)
164 m_aStates
.top().setDestination(Destination::PICT
); // as character
166 m_aStates
.top().setDestination(
167 Destination::SHAPEPROPERTYVALUEPICT
); // anchored inside a shape
169 case RTFKeyword::PICPROP
:
170 m_aStates
.top().setDestination(Destination::PICPROP
);
173 m_aStates
.top().setDestination(Destination::SHAPEPROPERTY
);
176 m_aStates
.top().setDestination(Destination::SHAPEPROPERTYNAME
);
179 m_aStates
.top().setDestination(Destination::SHAPEPROPERTYVALUE
);
181 case RTFKeyword::SHP
:
182 m_bNeedCrOrig
= m_bNeedCr
;
183 m_aStates
.top().setDestination(Destination::SHAPE
);
184 m_aStates
.top().setInShape(true);
186 case RTFKeyword::SHPINST
:
187 m_aStates
.top().setDestination(Destination::SHAPEINSTRUCTION
);
189 case RTFKeyword::NESTTABLEPROPS
:
190 // do not set any properties of outer table at nested table!
191 m_aStates
.top().getTableCellSprms() = m_aDefaultState
.getTableCellSprms();
192 m_aStates
.top().getTableCellAttributes() = m_aDefaultState
.getTableCellAttributes();
193 m_aNestedTableCellsSprms
.clear();
194 m_aNestedTableCellsAttributes
.clear();
196 m_aStates
.top().setDestination(Destination::NESTEDTABLEPROPERTIES
);
198 case RTFKeyword::HEADER
:
199 case RTFKeyword::FOOTER
:
200 case RTFKeyword::HEADERL
:
201 case RTFKeyword::HEADERR
:
202 case RTFKeyword::HEADERF
:
203 case RTFKeyword::FOOTERL
:
204 case RTFKeyword::FOOTERR
:
205 case RTFKeyword::FOOTERF
:
209 std::size_t nPos
= m_nGroupStartPos
- 1;
212 case RTFKeyword::HEADER
:
215 nId
= NS_ooxml::LN_headerr
;
219 case RTFKeyword::FOOTER
:
222 nId
= NS_ooxml::LN_footerr
;
226 case RTFKeyword::HEADERL
:
227 nId
= NS_ooxml::LN_headerl
;
229 case RTFKeyword::HEADERR
:
230 nId
= NS_ooxml::LN_headerr
;
232 case RTFKeyword::HEADERF
:
235 nId
= NS_ooxml::LN_headerf
;
239 case RTFKeyword::FOOTERL
:
240 nId
= NS_ooxml::LN_footerl
;
242 case RTFKeyword::FOOTERR
:
243 nId
= NS_ooxml::LN_footerr
;
245 case RTFKeyword::FOOTERF
:
248 nId
= NS_ooxml::LN_footerf
;
257 m_nHeaderFooterPositions
.push(std::make_pair(nId
, nPos
));
259 m_aStates
.top().setDestination(Destination::SKIP
);
262 case RTFKeyword::FOOTNOTE
:
266 Id nId
= NS_ooxml::LN_footnote
;
268 // Check if this is an endnote.
271 sal_uInt64
const nCurrent
= Strm().Tell();
272 for (int i
= 0; i
< 7; ++i
)
277 Strm().Seek(nCurrent
);
278 OString aKeyword
= aBuf
.makeStringAndClear();
279 if (aKeyword
== "\\ftnalt")
280 nId
= NS_ooxml::LN_endnote
;
282 if (m_aStates
.top().getCurrentBuffer() == &m_aSuperBuffer
)
283 m_aStates
.top().setCurrentBuffer(nullptr);
284 bool bCustomMark
= false;
285 OUString aCustomMark
;
286 for (auto const& elem
: m_aSuperBuffer
)
288 if (std::get
<0>(elem
) == BUFFER_UTEXT
)
290 aCustomMark
= std::get
<1>(elem
)->getString();
294 m_aSuperBuffer
.clear();
295 m_aStates
.top().setDestination(Destination::FOOTNOTE
);
296 Mapper().startCharacterGroup();
298 if (!m_aStates
.top().getCurrentBuffer())
299 resolveSubstream(m_nGroupStartPos
- 1, nId
, aCustomMark
);
302 RTFSprms aAttributes
;
303 aAttributes
.set(Id(0), new RTFValue(m_nGroupStartPos
- 1));
304 aAttributes
.set(Id(1), new RTFValue(nId
));
305 aAttributes
.set(Id(2), new RTFValue(aCustomMark
));
306 m_aStates
.top().getCurrentBuffer()->push_back(
307 Buf_t(BUFFER_RESOLVESUBSTREAM
, new RTFValue(aAttributes
), nullptr));
311 m_aStates
.top().getCharacterAttributes().clear();
312 m_aStates
.top().getCharacterSprms().clear();
313 auto pValue
= new RTFValue(1);
314 m_aStates
.top().getCharacterAttributes().set(
315 NS_ooxml::LN_CT_FtnEdnRef_customMarkFollows
, pValue
);
318 Mapper().endCharacterGroup();
319 m_aStates
.top().setDestination(Destination::SKIP
);
322 case RTFKeyword::BKMKSTART
:
323 m_aStates
.top().setDestination(Destination::BOOKMARKSTART
);
325 case RTFKeyword::BKMKEND
:
326 m_aStates
.top().setDestination(Destination::BOOKMARKEND
);
329 m_aStates
.top().setDestination(Destination::INDEXENTRY
);
332 case RTFKeyword::TCN
:
333 m_aStates
.top().setDestination(Destination::TOCENTRY
);
335 case RTFKeyword::REVTBL
:
336 m_aStates
.top().setDestination(Destination::REVISIONTABLE
);
338 case RTFKeyword::ANNOTATION
:
341 if (!m_aStates
.top().getCurrentBuffer())
343 resolveSubstream(m_nGroupStartPos
- 1, NS_ooxml::LN_annotation
);
347 RTFSprms aAttributes
;
348 aAttributes
.set(Id(0), new RTFValue(m_nGroupStartPos
- 1));
349 aAttributes
.set(Id(1), new RTFValue(NS_ooxml::LN_annotation
));
350 aAttributes
.set(Id(2), new RTFValue(OUString()));
351 m_aStates
.top().getCurrentBuffer()->push_back(
352 Buf_t(BUFFER_RESOLVESUBSTREAM
, new RTFValue(aAttributes
), nullptr));
354 m_aStates
.top().setDestination(Destination::SKIP
);
358 // If there is an author set, emit it now.
359 if (!m_aAuthor
.isEmpty() || !m_aAuthorInitials
.isEmpty())
361 RTFSprms aAttributes
;
362 if (!m_aAuthor
.isEmpty())
364 auto pValue
= new RTFValue(m_aAuthor
);
365 aAttributes
.set(NS_ooxml::LN_CT_TrackChange_author
, pValue
);
367 if (!m_aAuthorInitials
.isEmpty())
369 auto pValue
= new RTFValue(m_aAuthorInitials
);
370 aAttributes
.set(NS_ooxml::LN_CT_Comment_initials
, pValue
);
372 writerfilter::Reference
<Properties
>::Pointer_t pProperties
373 = new RTFReferenceProperties(std::move(aAttributes
));
374 Mapper().props(pProperties
);
378 case RTFKeyword::SHPTXT
:
379 case RTFKeyword::DPTXBXTEXT
:
381 bool bPictureFrame
= false;
382 for (const auto& rProperty
: m_aStates
.top().getShape().getProperties())
384 if (rProperty
.first
== "shapeType"
386 == std::u16string_view(
387 OUString::number(ESCHER_ShpInst_PictureFrame
)))
389 bPictureFrame
= true;
394 // Skip text on picture frames.
395 m_aStates
.top().setDestination(Destination::SKIP
);
398 m_aStates
.top().setDestination(Destination::SHAPETEXT
);
400 dispatchFlag(RTFKeyword::PARD
);
402 if (nKeyword
== RTFKeyword::SHPTXT
)
404 if (!m_aStates
.top().getCurrentBuffer())
405 m_pSdrImport
->resolve(m_aStates
.top().getShape(), false,
406 RTFSdrImport::SHAPE
);
409 auto pValue
= new RTFValue(m_aStates
.top().getShape());
410 m_aStates
.top().getCurrentBuffer()->push_back(
411 Buf_t(BUFFER_STARTSHAPE
, pValue
, nullptr));
417 case RTFKeyword::FORMFIELD
:
418 if (m_aStates
.top().getDestination() == Destination::FIELDINSTRUCTION
)
419 m_aStates
.top().setDestination(Destination::FORMFIELD
);
421 case RTFKeyword::FFNAME
:
422 m_aStates
.top().setDestination(Destination::FORMFIELDNAME
);
424 case RTFKeyword::FFL
:
425 m_aStates
.top().setDestination(Destination::FORMFIELDLIST
);
427 case RTFKeyword::DATAFIELD
:
428 m_aStates
.top().setDestination(Destination::DATAFIELD
);
430 case RTFKeyword::INFO
:
431 m_aStates
.top().setDestination(Destination::INFO
);
433 case RTFKeyword::CREATIM
:
434 m_aStates
.top().setDestination(Destination::CREATIONTIME
);
436 case RTFKeyword::REVTIM
:
437 m_aStates
.top().setDestination(Destination::REVISIONTIME
);
439 case RTFKeyword::PRINTIM
:
440 m_aStates
.top().setDestination(Destination::PRINTTIME
);
442 case RTFKeyword::AUTHOR
:
443 m_aStates
.top().setDestination(Destination::AUTHOR
);
445 case RTFKeyword::KEYWORDS
:
446 m_aStates
.top().setDestination(Destination::KEYWORDS
);
448 case RTFKeyword::OPERATOR
:
449 m_aStates
.top().setDestination(Destination::OPERATOR
);
451 case RTFKeyword::COMPANY
:
452 m_aStates
.top().setDestination(Destination::COMPANY
);
454 case RTFKeyword::COMMENT
:
455 m_aStates
.top().setDestination(Destination::COMMENT
);
457 case RTFKeyword::OBJECT
:
459 // beginning of an OLE Object
460 m_aStates
.top().setDestination(Destination::OBJECT
);
462 // check if the object is in a special container (e.g. a table)
463 if (!m_aStates
.top().getCurrentBuffer())
465 // the object is in a table or another container.
466 // Don't try to treat it as an OLE object (fdo#53594).
467 // Use the \result (RTFKeyword::RESULT) element of the object instead,
468 // the result element contain picture representing the OLE Object.
473 case RTFKeyword::OBJDATA
:
474 // check if the object is in a special container (e.g. a table)
475 if (m_aStates
.top().getCurrentBuffer())
477 // the object is in a table or another container.
478 // Use the \result (RTFKeyword::RESULT) element of the object instead,
480 m_aStates
.top().setDestination(Destination::SKIP
);
484 m_aStates
.top().setDestination(Destination::OBJDATA
);
487 case RTFKeyword::OBJCLASS
:
488 m_aStates
.top().setDestination(Destination::OBJCLASS
);
490 case RTFKeyword::RESULT
:
491 m_aStates
.top().setDestination(Destination::RESULT
);
493 case RTFKeyword::ATNDATE
:
494 m_aStates
.top().setDestination(Destination::ANNOTATIONDATE
);
496 case RTFKeyword::ATNAUTHOR
:
497 m_aStates
.top().setDestination(Destination::ANNOTATIONAUTHOR
);
499 case RTFKeyword::ATNREF
:
500 m_aStates
.top().setDestination(Destination::ANNOTATIONREFERENCE
);
502 case RTFKeyword::FALT
:
503 m_aStates
.top().setDestination(Destination::FALT
);
505 case RTFKeyword::FLYMAINCNT
:
506 m_aStates
.top().setDestination(Destination::FLYMAINCONTENT
);
508 case RTFKeyword::LISTTEXT
:
509 // Should be ignored by any reader that understands Word 97 through Word 2007 numbering.
510 case RTFKeyword::NONESTTABLES
:
511 // This destination should be ignored by readers that support nested tables.
512 m_aStates
.top().setDestination(Destination::SKIP
);
515 m_aStates
.top().setDestination(Destination::DRAWINGOBJECT
);
518 m_aStates
.top().setDestination(Destination::PARAGRAPHNUMBERING
);
520 case RTFKeyword::PNTEXT
:
521 // This destination should be ignored by readers that support paragraph numbering.
522 m_aStates
.top().setDestination(Destination::SKIP
);
524 case RTFKeyword::PNTXTA
:
525 m_aStates
.top().setDestination(Destination::PARAGRAPHNUMBERING_TEXTAFTER
);
527 case RTFKeyword::PNTXTB
:
528 m_aStates
.top().setDestination(Destination::PARAGRAPHNUMBERING_TEXTBEFORE
);
530 case RTFKeyword::TITLE
:
531 m_aStates
.top().setDestination(Destination::TITLE
);
533 case RTFKeyword::SUBJECT
:
534 m_aStates
.top().setDestination(Destination::SUBJECT
);
536 case RTFKeyword::DOCCOMM
:
537 m_aStates
.top().setDestination(Destination::DOCCOMM
);
539 case RTFKeyword::ATRFSTART
:
540 m_aStates
.top().setDestination(Destination::ANNOTATIONREFERENCESTART
);
542 case RTFKeyword::ATRFEND
:
543 m_aStates
.top().setDestination(Destination::ANNOTATIONREFERENCEEND
);
545 case RTFKeyword::ATNID
:
546 m_aStates
.top().setDestination(Destination::ATNID
);
548 case RTFKeyword::MMATH
:
549 case RTFKeyword::MOMATHPARA
:
550 // Nothing to do here (just enter the destination) till RTFKeyword::MMATHPR is implemented.
553 m_aStates
.top().setDestination(Destination::MR
);
555 case RTFKeyword::MCHR
:
556 m_aStates
.top().setDestination(Destination::MCHR
);
558 case RTFKeyword::MPOS
:
559 m_aStates
.top().setDestination(Destination::MPOS
);
561 case RTFKeyword::MVERTJC
:
562 m_aStates
.top().setDestination(Destination::MVERTJC
);
564 case RTFKeyword::MSTRIKEH
:
565 m_aStates
.top().setDestination(Destination::MSTRIKEH
);
567 case RTFKeyword::MDEGHIDE
:
568 m_aStates
.top().setDestination(Destination::MDEGHIDE
);
570 case RTFKeyword::MTYPE
:
571 m_aStates
.top().setDestination(Destination::MTYPE
);
573 case RTFKeyword::MGROW
:
574 m_aStates
.top().setDestination(Destination::MGROW
);
576 case RTFKeyword::MHIDETOP
:
577 case RTFKeyword::MHIDEBOT
:
578 case RTFKeyword::MHIDELEFT
:
579 case RTFKeyword::MHIDERIGHT
:
580 // SmOoxmlImport::handleBorderBox will ignore these anyway, so silently ignore for now.
581 m_aStates
.top().setDestination(Destination::SKIP
);
583 case RTFKeyword::MSUBHIDE
:
584 m_aStates
.top().setDestination(Destination::MSUBHIDE
);
586 case RTFKeyword::MSUPHIDE
:
587 m_aStates
.top().setDestination(Destination::MSUPHIDE
);
589 case RTFKeyword::MBEGCHR
:
590 m_aStates
.top().setDestination(Destination::MBEGCHR
);
592 case RTFKeyword::MSEPCHR
:
593 m_aStates
.top().setDestination(Destination::MSEPCHR
);
595 case RTFKeyword::MENDCHR
:
596 m_aStates
.top().setDestination(Destination::MENDCHR
);
598 case RTFKeyword::UPR
:
599 m_aStates
.top().setDestination(Destination::UPR
);
602 // Anything inside \ud is just normal Unicode content.
603 m_aStates
.top().setDestination(Destination::NORMAL
);
605 case RTFKeyword::BACKGROUND
:
606 m_aStates
.top().setDestination(Destination::BACKGROUND
);
607 m_aStates
.top().setInBackground(true);
609 case RTFKeyword::SHPGRP
:
611 RTFLookahead
aLookahead(Strm(), m_pTokenizer
->getGroupStart());
612 if (!aLookahead
.hasTable())
614 uno::Reference
<drawing::XShapes
> xGroupShape(
615 m_xModelFactory
->createInstance("com.sun.star.drawing.GroupShape"),
617 uno::Reference
<drawing::XDrawPageSupplier
> xDrawSupplier(m_xDstDoc
,
619 if (xDrawSupplier
.is())
621 uno::Reference
<drawing::XShape
> xShape(xGroupShape
, uno::UNO_QUERY
);
622 // set default VertOrient before inserting
623 uno::Reference
<beans::XPropertySet
>(xShape
, uno::UNO_QUERY_THROW
)
624 ->setPropertyValue("VertOrient", uno::Any(text::VertOrientation::NONE
));
625 xDrawSupplier
->getDrawPage()->add(xShape
);
627 m_pSdrImport
->pushParent(xGroupShape
);
628 m_aStates
.top().setCreatedShapeGroup(true);
630 m_aStates
.top().setDestination(Destination::SHAPEGROUP
);
631 m_aStates
.top().setInShapeGroup(true);
634 case RTFKeyword::FTNSEP
:
635 m_aStates
.top().setDestination(Destination::FOOTNOTESEPARATOR
);
636 m_aStates
.top().getCharacterAttributes().set(
637 NS_ooxml::LN_CT_FtnEdn_type
,
638 new RTFValue(NS_ooxml::LN_Value_doc_ST_FtnEdn_separator
));
640 case RTFKeyword::USERPROPS
:
641 // Container of all user-defined properties.
642 m_aStates
.top().setDestination(Destination::USERPROPS
);
643 if (m_xDocumentProperties
.is())
644 // Create a custom document properties to be able to process them later all at once.
645 m_xDocumentProperties
= document::DocumentProperties::create(m_xContext
);
647 case RTFKeyword::PROPNAME
:
648 m_aStates
.top().setDestination(Destination::PROPNAME
);
650 case RTFKeyword::STATICVAL
:
651 m_aStates
.top().setDestination(Destination::STATICVAL
);
653 case RTFKeyword::GENERATOR
:
654 m_aStates
.top().setDestination(Destination::GENERATOR
);
658 // Check if it's a math token.
659 RTFMathSymbol
aSymbol(nKeyword
);
660 if (RTFTokenizer::lookupMathKeyword(aSymbol
))
662 m_aMathBuffer
.appendOpeningTag(aSymbol
.GetToken());
663 m_aStates
.top().setDestination(aSymbol
.GetDestination());
667 SAL_INFO("writerfilter",
668 "TODO handle destination '" << keywordToString(nKeyword
) << "'");
669 // Make sure we skip destinations (even without \*) till we don't handle them
670 m_aStates
.top().setDestination(Destination::SKIP
);
671 aSkip
.setParsed(false);
676 // new destination => use new destination text
677 m_aStates
.top().setCurrentDestinationText(&m_aStates
.top().getDestinationText());
682 } // namespace writerfilter
684 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */