tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / xmloff / source / text / XMLTextMarkImportContext.cxx
blob392ef6c440f05021c9c71e8d6721bfa659e461a8
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 "XMLTextMarkImportContext.hxx"
23 #include <rtl/ustring.hxx>
24 #include <sal/log.hxx>
25 #include <osl/diagnose.h>
26 #include <sax/tools/converter.hxx>
27 #include <xmloff/xmluconv.hxx>
28 #include <xmloff/xmltoken.hxx>
29 #include <xmloff/xmlimp.hxx>
30 #include <xmloff/xmlnamespace.hxx>
31 #include <xmloff/odffields.hxx>
32 #include <xmloff/xmlement.hxx>
33 #include <com/sun/star/frame/XModel.hpp>
34 #include <com/sun/star/text/ControlCharacter.hpp>
35 #include <com/sun/star/text/XTextContent.hpp>
36 #include <com/sun/star/text/XTextRangeCompare.hpp>
37 #include <com/sun/star/beans/XPropertySet.hpp>
38 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
39 #include <com/sun/star/container/XNamed.hpp>
40 #include <com/sun/star/rdf/XMetadatable.hpp>
42 #include <com/sun/star/text/XFormField.hpp>
43 #include <comphelper/diagnose_ex.hxx>
45 #include <RDFaImportHelper.hxx>
48 using namespace ::com::sun::star;
49 using namespace ::com::sun::star::text;
50 using namespace ::com::sun::star::uno;
51 using namespace ::com::sun::star::beans;
52 using namespace ::com::sun::star::lang;
53 using namespace ::com::sun::star::container;
54 using namespace ::com::sun::star::xml::sax;
55 using namespace ::xmloff::token;
58 XMLFieldParamImportContext::XMLFieldParamImportContext(
59 SvXMLImport& rImport,
60 XMLTextImportHelper& rHlp ) :
61 SvXMLImportContext(rImport),
62 rHelper(rHlp)
67 void XMLFieldParamImportContext::startFastElement(sal_Int32 /*nElement*/, const css::uno::Reference< css::xml::sax::XFastAttributeList> & xAttrList)
69 OUString sName;
70 OUString sValue;
72 for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ))
74 switch (aIter.getToken())
76 case XML_ELEMENT(FIELD, XML_NAME):
77 sName = aIter.toString();
78 break;
79 case XML_ELEMENT(FIELD, XML_VALUE):
80 sValue = aIter.toString();
81 break;
82 default:
83 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
86 if (rHelper.hasCurrentFieldCtx() && !sName.isEmpty()) {
87 rHelper.addFieldParam(sName, sValue);
92 XMLTextMarkImportContext::XMLTextMarkImportContext(
93 SvXMLImport& rImport,
94 XMLTextImportHelper& rHlp,
95 uno::Reference<uno::XInterface> & io_rxCrossRefHeadingBookmark )
96 : SvXMLImportContext(rImport)
97 , m_rHelper(rHlp)
98 , m_rxCrossRefHeadingBookmark(io_rxCrossRefHeadingBookmark)
99 , m_isHidden(false)
100 , m_bHaveAbout(false)
104 namespace {
106 enum lcl_MarkType { TypeReference, TypeReferenceStart, TypeReferenceEnd,
107 TypeBookmark, TypeBookmarkStart, TypeBookmarkEnd,
108 TypeFieldmark, TypeFieldmarkStart, TypeFieldmarkSeparator, TypeFieldmarkEnd
113 SvXMLEnumMapEntry<lcl_MarkType> const lcl_aMarkTypeMap[] =
115 { XML_REFERENCE_MARK, TypeReference },
116 { XML_REFERENCE_MARK_START, TypeReferenceStart },
117 { XML_REFERENCE_MARK_END, TypeReferenceEnd },
118 { XML_BOOKMARK, TypeBookmark },
119 { XML_BOOKMARK_START, TypeBookmarkStart },
120 { XML_BOOKMARK_END, TypeBookmarkEnd },
121 { XML_FIELDMARK, TypeFieldmark },
122 { XML_FIELDMARK_START, TypeFieldmarkStart },
123 { XML_FIELDMARK_SEPARATOR, TypeFieldmarkSeparator },
124 { XML_FIELDMARK_END, TypeFieldmarkEnd },
125 { XML_TOKEN_INVALID, lcl_MarkType(0) },
129 static const OUString & lcl_getFormFieldmarkName(std::u16string_view name)
131 if (name == ODF_FORMCHECKBOX ||
132 name == u"msoffice.field.FORMCHECKBOX" ||
133 name == u"ecma.office-open-xml.field.FORMCHECKBOX")
134 return ODF_FORMCHECKBOX;
135 else if (name == ODF_FORMDROPDOWN ||
136 name == u"ecma.office-open-xml.field.FORMDROPDOWN")
137 return ODF_FORMDROPDOWN;
138 else
139 return EMPTY_OUSTRING;
142 static OUString lcl_getFieldmarkName(OUString const& name)
144 if (name == "msoffice.field.FORMTEXT" ||
145 name == "ecma.office-open-xml.field.FORMTEXT")
146 return ODF_FORMTEXT;
147 else
148 return name;
152 void XMLTextMarkImportContext::startFastElement( sal_Int32 nElement,
153 const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
155 if (!FindName(xAttrList))
157 m_sBookmarkName.clear();
160 if ((nElement & TOKEN_MASK) == XML_FIELDMARK_START ||
161 (nElement & TOKEN_MASK) == XML_FIELDMARK)
163 if (m_sBookmarkName.isEmpty())
165 m_sBookmarkName = "Unknown";
167 m_rHelper.pushFieldCtx( m_sBookmarkName, m_sFieldName );
170 if ((nElement & TOKEN_MASK) == XML_BOOKMARK_START)
172 m_rHelper.setBookmarkAttributes(m_sBookmarkName, m_isHidden, m_sCondition);
176 static auto InsertFieldmark(SvXMLImport & rImport,
177 XMLTextImportHelper & rHelper, bool const isFieldmarkSeparatorMissing) -> void
179 assert(rHelper.hasCurrentFieldCtx()); // was set up in StartElement()
181 // fdo#86795 check if it's actually a checkbox first
182 auto const [ name, type ] = rHelper.getCurrentFieldType();
183 OUString const fieldmarkTypeName = lcl_getFieldmarkName(type);
184 if (fieldmarkTypeName == ODF_FORMCHECKBOX ||
185 fieldmarkTypeName == ODF_FORMDROPDOWN)
186 { // sw can't handle checkbox with start+end
187 SAL_INFO("xmloff.text", "invalid fieldmark-start/fieldmark-end ignored");
188 return;
191 uno::Reference<text::XTextRange> const xStartRange(rHelper.getCurrentFieldStart());
192 uno::Reference<text::XTextCursor> const xCursor(
193 rHelper.GetText()->createTextCursorByRange(xStartRange));
194 uno::Reference<text::XTextRangeCompare> const xCompare(rHelper.GetText(), uno::UNO_QUERY);
195 if (xCompare->compareRegionStarts(xStartRange, rHelper.GetCursorAsRange()) < 0)
197 SAL_WARN("xmloff.text", "invalid field mark positions");
198 assert(false);
200 xCursor->gotoRange(rHelper.GetCursorAsRange(), true);
202 Reference<XTextContent> const xContent = XMLTextMarkImportContext::CreateAndInsertMark(
203 rImport, u"com.sun.star.text.Fieldmark"_ustr, name, xCursor,
204 OUString(), isFieldmarkSeparatorMissing);
206 if (!xContent.is())
207 return;
209 // setup fieldmark...
210 Reference<text::XFormField> const xFormField(xContent, UNO_QUERY);
211 assert(xFormField.is());
212 try {
213 xFormField->setFieldType(fieldmarkTypeName);
214 } catch (uno::RuntimeException const&) {
215 // tdf#140437 somehow old documents had the field code in the type
216 // attribute instead of field:param
217 SAL_INFO("xmloff.text", "invalid fieldmark type, converting to param");
218 // add without checking: FieldParamImporter::Import() catches ElementExistException
219 rHelper.addFieldParam(ODF_CODE_PARAM, fieldmarkTypeName);
220 xFormField->setFieldType(ODF_UNHANDLED);
222 rHelper.setCurrentFieldParamsTo(xFormField);
223 // move cursor after setFieldType as that may delete/re-insert
224 rHelper.GetCursor()->gotoRange(xContent->getAnchor()->getEnd(), false);
225 rHelper.GetCursor()->goLeft(1, false); // move before CH_TXT_ATR_FIELDEND
226 // tdf#129520: AppendTextNode() ignores the content index!
227 // plan B: insert a spurious paragraph break now and join
228 // it in PopFieldmark()!
229 rHelper.GetText()->insertControlCharacter(rHelper.GetCursor(),
230 text::ControlCharacter::PARAGRAPH_BREAK, false);
231 rHelper.GetCursor()->goLeft(1, false); // back to previous paragraph
234 static auto PopFieldmark(XMLTextImportHelper & rHelper) -> void
236 // can't verify name because it's not written as an attribute...
237 uno::Reference<text::XTextContent> const xField(rHelper.popFieldCtx(),
238 uno::UNO_QUERY);
239 if (!xField.is())
240 return;
242 if (rHelper.GetText() == xField->getAnchor()->getText())
245 { // skip CH_TXT_ATR_FIELDEND
246 rHelper.GetCursor()->goRight(1, true);
247 rHelper.GetCursor()->setString(OUString()); // undo AppendTextNode from InsertFieldmark
248 rHelper.GetCursor()->gotoRange(xField->getAnchor()->getEnd(), false);
250 catch (uno::Exception const&)
252 assert(false); // must succeed
255 else
257 SAL_INFO("xmloff.text", "fieldmark has invalid positions");
258 // could either dispose it or leave it to end at the end of the document?
259 xField->dispose();
263 void XMLTextMarkImportContext::endFastElement(sal_Int32 nElement)
265 static constexpr OUString sAPI_bookmark = u"com.sun.star.text.Bookmark"_ustr;
267 lcl_MarkType nTmp{};
268 if (!SvXMLUnitConverter::convertEnum(nTmp, SvXMLImport::getNameFromToken(nElement), lcl_aMarkTypeMap))
269 return;
271 if (m_sBookmarkName.isEmpty() && TypeFieldmarkEnd != nTmp && TypeFieldmarkSeparator != nTmp)
272 return;
274 switch (nTmp)
276 case TypeReference:
277 // export point reference mark
278 CreateAndInsertMark(GetImport(),
279 u"com.sun.star.text.ReferenceMark"_ustr,
280 m_sBookmarkName,
281 m_rHelper.GetCursorAsRange()->getStart());
282 break;
284 case TypeBookmark:
286 // tdf#94804: detect duplicate heading cross reference bookmarks
287 if (m_sBookmarkName.startsWith("__RefHeading__"))
289 if (m_rxCrossRefHeadingBookmark.is())
291 uno::Reference<container::XNamed> const xNamed(
292 m_rxCrossRefHeadingBookmark, uno::UNO_QUERY);
293 m_rHelper.AddCrossRefHeadingMapping(
294 m_sBookmarkName, xNamed->getName());
295 break; // don't insert
299 [[fallthrough]];
300 case TypeFieldmark:
302 const OUString formFieldmarkName=lcl_getFormFieldmarkName(m_sFieldName);
303 bool bImportAsField = (nTmp==TypeFieldmark && !formFieldmarkName.isEmpty()); //@TODO handle abbreviation cases...
304 // export point bookmark
305 const Reference<XInterface> xContent(
306 CreateAndInsertMark(GetImport(),
307 (bImportAsField ? u"com.sun.star.text.FormFieldmark"_ustr : sAPI_bookmark),
308 m_sBookmarkName,
309 m_rHelper.GetCursorAsRange()->getStart(),
310 m_sXmlId) );
311 if (nTmp==TypeFieldmark) {
312 if (xContent.is() && bImportAsField) {
313 // setup fieldmark...
314 Reference< css::text::XFormField> xFormField(xContent, UNO_QUERY);
315 xFormField->setFieldType(formFieldmarkName);
316 if (xFormField.is() && m_rHelper.hasCurrentFieldCtx()) {
317 m_rHelper.setCurrentFieldParamsTo(xFormField);
320 m_rHelper.popFieldCtx();
322 if (TypeBookmark == nTmp
323 && m_sBookmarkName.startsWith("__RefHeading__"))
325 assert(xContent.is());
326 m_rxCrossRefHeadingBookmark = xContent;
329 break;
331 case TypeBookmarkStart:
332 // save XTextRange for later construction of bookmark
334 std::shared_ptr< ::xmloff::ParsedRDFaAttributes >
335 xRDFaAttributes;
336 if (m_bHaveAbout && TypeBookmarkStart == nTmp)
338 xRDFaAttributes =
339 GetImport().GetRDFaImportHelper().ParseRDFa(
340 m_sAbout, m_sProperty,
341 m_sContent, m_sDatatype);
343 m_rHelper.InsertBookmarkStartRange(
344 m_sBookmarkName,
345 m_rHelper.GetCursorAsRange()->getStart(),
346 m_sXmlId, xRDFaAttributes);
348 break;
350 case TypeBookmarkEnd:
352 // tdf#94804: detect duplicate heading cross reference bookmarks
353 if (m_sBookmarkName.startsWith("__RefHeading__"))
355 if (m_rxCrossRefHeadingBookmark.is())
357 uno::Reference<container::XNamed> const xNamed(
358 m_rxCrossRefHeadingBookmark, uno::UNO_QUERY);
359 m_rHelper.AddCrossRefHeadingMapping(
360 m_sBookmarkName, xNamed->getName());
361 break; // don't insert
365 // get old range, and construct
366 Reference<XTextRange> xStartRange;
367 std::shared_ptr< ::xmloff::ParsedRDFaAttributes >
368 xRDFaAttributes;
369 if (m_rHelper.FindAndRemoveBookmarkStartRange(
370 m_sBookmarkName, xStartRange,
371 m_sXmlId, xRDFaAttributes))
373 Reference<XTextRange> xEndRange(
374 m_rHelper.GetCursorAsRange()->getStart());
376 // check if beginning and end are in same XText
377 if (xStartRange.is() && xEndRange.is() && xStartRange->getText() == xEndRange->getText())
379 // create range for insertion
380 Reference<XTextCursor> xInsertionCursor =
381 m_rHelper.GetText()->createTextCursorByRange(
382 xEndRange);
383 try {
384 xInsertionCursor->gotoRange(xStartRange, true);
385 } catch (uno::Exception&) {
386 TOOLS_WARN_EXCEPTION("xmloff.text",
387 "cannot go to end position of bookmark");
390 //DBG_ASSERT(! xInsertionCursor->isCollapsed(),
391 // "we want no point mark");
392 // can't assert, because someone could
393 // create a file with subsequence
394 // start/end elements
396 Reference<XInterface> xContent;
397 // insert reference
398 xContent = CreateAndInsertMark(GetImport(),
399 sAPI_bookmark,
400 m_sBookmarkName,
401 xInsertionCursor,
402 m_sXmlId);
403 if (xRDFaAttributes)
405 const Reference<rdf::XMetadatable>
406 xMeta(xContent, UNO_QUERY);
407 GetImport().GetRDFaImportHelper().AddRDFa(
408 xMeta, xRDFaAttributes);
410 const Reference<XPropertySet> xPropertySet(xContent, UNO_QUERY);
411 if (xPropertySet.is())
413 xPropertySet->setPropertyValue(u"BookmarkHidden"_ustr, uno::Any(m_rHelper.getBookmarkHidden(m_sBookmarkName)));
414 xPropertySet->setPropertyValue(u"BookmarkCondition"_ustr, uno::Any(m_rHelper.getBookmarkCondition(m_sBookmarkName)));
416 if (m_sBookmarkName.startsWith("__RefHeading__"))
418 assert(xContent.is());
419 m_rxCrossRefHeadingBookmark = std::move(xContent);
422 // else: beginning/end in different XText -> ignore!
424 // else: no start found -> ignore!
425 break;
427 case TypeFieldmarkStart:
429 break;
431 case TypeFieldmarkSeparator:
433 InsertFieldmark(GetImport(), m_rHelper, false);
434 break;
436 case TypeFieldmarkEnd:
438 if (m_rHelper.hasCurrentFieldCtx() && !m_rHelper.hasCurrentFieldSeparator())
439 { // backward compat for old files without separator
440 InsertFieldmark(GetImport(), m_rHelper, true);
442 PopFieldmark(m_rHelper);
443 break;
445 case TypeReferenceStart:
446 case TypeReferenceEnd:
447 OSL_FAIL("reference start/end are handled in txtparai !");
448 break;
450 default:
451 OSL_FAIL("unknown mark type");
452 break;
456 css::uno::Reference< css::xml::sax::XFastContextHandler > XMLTextMarkImportContext::createFastChildContext(
457 sal_Int32 ,
458 const css::uno::Reference< css::xml::sax::XFastAttributeList >& )
460 return new XMLFieldParamImportContext(GetImport(), m_rHelper);
464 Reference<XTextContent> XMLTextMarkImportContext::CreateAndInsertMark(
465 SvXMLImport& rImport,
466 const OUString& sServiceName,
467 const OUString& sMarkName,
468 const Reference<XTextRange> & rRange,
469 const OUString& i_rXmlId,
470 bool const isFieldmarkSeparatorMissing)
472 // create mark
473 const Reference<XMultiServiceFactory> xFactory(rImport.GetModel(),
474 UNO_QUERY);
475 Reference<XInterface> xIfc;
477 if (xFactory.is())
479 xIfc = xFactory->createInstance(sServiceName);
481 if (!xIfc.is())
483 OSL_FAIL("CreateAndInsertMark: cannot create service?");
484 return nullptr;
487 // set name (unless there is no name (text:meta))
488 const Reference<XNamed> xNamed(xIfc, UNO_QUERY);
489 if (xNamed.is())
491 xNamed->setName(sMarkName);
493 else
495 if (!sMarkName.isEmpty())
497 OSL_FAIL("name given, but XNamed not supported?");
498 return nullptr;
502 if (isFieldmarkSeparatorMissing)
504 uno::Reference<beans::XPropertySet> const xProps(xIfc, uno::UNO_QUERY_THROW);
505 xProps->setPropertyValue(u"PrivateSeparatorAtStart"_ustr, uno::Any(true));
508 // cast to XTextContent and attach to document
509 const Reference<XTextContent> xTextContent(xIfc, UNO_QUERY);
510 if (xTextContent.is())
514 // if inserting marks, bAbsorb==sal_False will cause
515 // collapsing of the given XTextRange.
516 rImport.GetTextImport()->GetText()->insertTextContent(rRange,
517 xTextContent, true);
519 // xml:id for RDF metadata -- after insertion!
520 rImport.SetXmlId(xIfc, i_rXmlId);
522 return xTextContent;
524 catch (css::lang::IllegalArgumentException &)
526 OSL_FAIL("CreateAndInsertMark: cannot insert?");
527 return nullptr;
531 return nullptr;
534 bool XMLTextMarkImportContext::FindName(
535 const Reference<XFastAttributeList> & xAttrList)
537 bool bNameOK = false;
539 // find name attribute first
540 for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
542 OUString sValue = aIter.toString();
543 switch(aIter.getToken())
545 case XML_ELEMENT(TEXT, XML_NAME):
546 m_sBookmarkName = sValue;
547 bNameOK = true;
548 break;
549 case XML_ELEMENT(XML, XML_ID):
550 m_sXmlId = sValue;
551 break;
552 // RDFa
553 case XML_ELEMENT(XHTML, XML_ABOUT):
554 m_sAbout = sValue;
555 m_bHaveAbout = true;
556 break;
557 case XML_ELEMENT(XHTML, XML_PROPERTY):
558 m_sProperty = sValue;
559 break;
560 case XML_ELEMENT(XHTML, XML_CONTENT):
561 m_sContent = sValue;
562 break;
563 case XML_ELEMENT(XHTML, XML_DATATYPE):
564 m_sDatatype = sValue;
565 break;
566 case XML_ELEMENT(FIELD, XML_TYPE):
567 m_sFieldName = sValue;
568 break;
569 case XML_ELEMENT(LO_EXT, XML_HIDDEN):
570 ::sax::Converter::convertBool(m_isHidden, sValue);
571 break;
572 case XML_ELEMENT(LO_EXT, XML_CONDITION):
573 m_sCondition = sValue;
574 break;
575 default:
576 XMLOFF_WARN_UNKNOWN("xmloff", aIter);
580 return bNameOK;
583 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */