/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <unotxdoc.hxx>
#include <map>
#include <utility>
#include <vector>
#include <com/sun/star/beans/XPropertyAccess.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/sequence.hxx>
#include <o3tl/string_view.hxx>
#include <tools/json_writer.hxx>
#include <tools/urlobj.hxx>
#include <xmloff/odffields.hxx>
#include <sfx2/lokhelper.hxx>
#include <IDocumentMarkAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <doc.hxx>
#include <docsh.hxx>
#include <fmtrfmrk.hxx>
#include <wrtsh.hxx>
#include <txtrfmrk.hxx>
#include <ndtxt.hxx>
#include <redline.hxx>
#include <unoredline.hxx>
#include <unoredlines.hxx>
#include <unoport.hxx>
#include <unoprnms.hxx>
#include <unocontentcontrol.hxx>
#include <rootfrm.hxx>
#include <pagefrm.hxx>
#include <com/sun/star/text/XTextContent.hpp>
#include <com/sun/star/text/XPageCursor.hpp>
#include <com/sun/star/text/XTextEmbeddedObjectsSupplier.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/chart2/XInternalDataProvider.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/chart/XChartDocument.hpp>
#include <com/sun/star/chart/XChartDataArray.hpp>
#include <com/sun/star/chart2/XTitle.hpp>
#include <com/sun/star/chart2/XTitled.hpp>
#include <com/sun/star/document/XDocumentProperties2.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <sax/tools/converter.hxx>
using namespace ::com::sun::star;
namespace
{
// A helper class to make it easier to put UNO property values to JSON with a given name.
// Removes noise from code.
class PropertyExtractor
{
public:
PropertyExtractor(const uno::Reference<beans::XPropertySet>& xProperties,
tools::JsonWriter& rWriter)
: m_xProperties(xProperties)
, m_rWriter(rWriter)
{
}
template <typename T> void extract(const OUString& unoName, std::string_view jsonName)
{
if (T val; m_xProperties->getPropertyValue(unoName) >>= val)
{
if constexpr (std::is_same_v<T, util::DateTime>)
{
OUStringBuffer buf(32);
sax::Converter::convertDateTime(buf, val, nullptr, true);
m_rWriter.put(jsonName, buf.makeStringAndClear());
}
else
m_rWriter.put(jsonName, val);
}
}
private:
uno::Reference<beans::XPropertySet> m_xProperties;
tools::JsonWriter& m_rWriter;
};
/// Implements getCommandValues(".uno:TextFormFields").
///
/// Parameters:
///
/// - type: e.g. ODF_UNHANDLED
/// - commandPrefix: field command prefix to not return all fieldmarks
void GetTextFormFields(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aType;
OUString aCommandPrefix;
{
auto it = rArguments.find(u"type"_ustr);
if (it != rArguments.end())
{
aType = it->second;
}
it = rArguments.find(u"commandPrefix"_ustr);
if (it != rArguments.end())
{
aCommandPrefix = it->second;
}
}
SwDoc* pDoc = pDocShell->GetDoc();
IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess();
auto aFields = rJsonWriter.startArray("fields");
for (auto it = pMarkAccess->getFieldmarksBegin(); it != pMarkAccess->getFieldmarksEnd(); ++it)
{
sw::mark::Fieldmark* pFieldmark = *it;
assert(pFieldmark);
if (pFieldmark->GetFieldname() != aType)
{
continue;
}
auto itParam = pFieldmark->GetParameters()->find(ODF_CODE_PARAM);
if (itParam == pFieldmark->GetParameters()->end())
{
continue;
}
OUString aCommand;
itParam->second >>= aCommand;
if (!aCommand.startsWith(aCommandPrefix))
{
continue;
}
auto aField = rJsonWriter.startStruct();
rJsonWriter.put("type", aType);
rJsonWriter.put("command", aCommand);
}
}
/// Implements getCommandValues(".uno:TextFormField").
///
/// Parameters:
///
/// - type: e.g. ODF_UNHANDLED
/// - commandPrefix: field command prefix to not return all fieldmarks
void GetTextFormField(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aType;
OUString aCommandPrefix;
auto it = rArguments.find(u"type"_ustr);
if (it != rArguments.end())
{
aType = it->second;
}
it = rArguments.find(u"commandPrefix"_ustr);
if (it != rArguments.end())
{
aCommandPrefix = it->second;
}
IDocumentMarkAccess& rIDMA = *pDocShell->GetDoc()->getIDocumentMarkAccess();
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
SwPosition& rCursor = *pWrtShell->GetCursor()->GetPoint();
sw::mark::Fieldmark* pFieldmark = rIDMA.getInnerFieldmarkFor(rCursor);
auto typeNode = rJsonWriter.startNode("field");
if (!pFieldmark)
{
return;
}
if (pFieldmark->GetFieldname() != aType)
{
return;
}
auto itParam = pFieldmark->GetParameters()->find(ODF_CODE_PARAM);
if (itParam == pFieldmark->GetParameters()->end())
{
return;
}
OUString aCommand;
itParam->second >>= aCommand;
if (!aCommand.startsWith(aCommandPrefix))
{
return;
}
rJsonWriter.put("type", aType);
rJsonWriter.put("command", aCommand);
}
/// Implements getCommandValues(".uno:SetDocumentProperties").
///
/// Parameters:
///
/// - namePrefix: field name prefix to not return all user-defined properties
void GetDocumentProperties(tools::JsonWriter& rJsonWriter, const SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aNamePrefix;
auto it = rArguments.find(u"namePrefix"_ustr);
if (it != rArguments.end())
{
aNamePrefix = it->second;
}
uno::Reference<document::XDocumentPropertiesSupplier> xDPS(pDocShell->GetModel(),
uno::UNO_QUERY);
uno::Reference<document::XDocumentProperties> xDP = xDPS->getDocumentProperties();
uno::Reference<beans::XPropertyAccess> xUDP(xDP->getUserDefinedProperties(), uno::UNO_QUERY);
auto aUDPs = comphelper::sequenceToContainer<std::vector<beans::PropertyValue>>(
xUDP->getPropertyValues());
auto aProperties = rJsonWriter.startArray("userDefinedProperties");
for (const auto& rUDP : aUDPs)
{
if (!rUDP.Name.startsWith(aNamePrefix))
{
continue;
}
if (rUDP.Value.getValueTypeClass() != uno::TypeClass_STRING)
{
continue;
}
OUString aValue;
rUDP.Value >>= aValue;
auto aProperty = rJsonWriter.startStruct();
rJsonWriter.put("name", rUDP.Name);
rJsonWriter.put("type", "string");
rJsonWriter.put("value", aValue);
}
}
/// Implements getCommandValues(".uno:Bookmarks").
///
/// Parameters:
///
/// - namePrefix: bookmark name prefix to not return all bookmarks
void GetBookmarks(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aNamePrefix;
{
auto it = rArguments.find(u"namePrefix"_ustr);
if (it != rArguments.end())
{
aNamePrefix = it->second;
}
}
IDocumentMarkAccess& rIDMA = *pDocShell->GetDoc()->getIDocumentMarkAccess();
auto aBookmarks = rJsonWriter.startArray("bookmarks");
for (auto it = rIDMA.getBookmarksBegin(); it != rIDMA.getBookmarksEnd(); ++it)
{
sw::mark::MarkBase* pMark = *it;
if (!pMark->GetName().toString().startsWith(aNamePrefix))
{
continue;
}
auto aProperty = rJsonWriter.startStruct();
rJsonWriter.put("name", pMark->GetName().toString());
}
}
/// Implements getCommandValues(".uno:Bookmark").
///
/// Parameters:
///
/// - namePrefix: bookmark name prefix to not return all bookmarks
void GetBookmark(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aNamePrefix;
{
auto it = rArguments.find(u"namePrefix"_ustr);
if (it != rArguments.end())
{
aNamePrefix = it->second;
}
}
IDocumentMarkAccess& rIDMA = *pDocShell->GetDoc()->getIDocumentMarkAccess();
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
SwPosition& rCursor = *pWrtShell->GetCursor()->GetPoint();
sw::mark::MarkBase* pBookmark = rIDMA.getOneInnermostBookmarkFor(rCursor);
auto aBookmark = rJsonWriter.startNode("bookmark");
if (!pBookmark)
{
return;
}
if (!pBookmark->GetName().toString().startsWith(aNamePrefix))
{
return;
}
rJsonWriter.put("name", pBookmark->GetName().toString());
}
/// Implements getCommandValues(".uno:Fields").
///
/// Parameters:
///
/// - typeName: field type condition to not return all fields
/// - namePrefix: field name prefix to not return all fields
void GetFields(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aTypeName;
{
auto it = rArguments.find(u"typeName"_ustr);
if (it != rArguments.end())
{
aTypeName = it->second;
}
}
// See SwFieldTypeFromString().
if (aTypeName != "SetRef")
{
return;
}
OUString aNamePrefix;
{
auto it = rArguments.find(u"namePrefix"_ustr);
if (it != rArguments.end())
{
aNamePrefix = it->second;
}
}
SwDoc* pDoc = pDocShell->GetDoc();
auto aBookmarks = rJsonWriter.startArray("setRefs");
std::vector<const SwFormatRefMark*> aRefMarks;
for (sal_uInt16 i = 0; i < pDoc->GetRefMarks(); ++i)
{
aRefMarks.push_back(pDoc->GetRefMark(i));
}
// Sort the refmarks based on their start position.
std::sort(aRefMarks.begin(), aRefMarks.end(),
[](const SwFormatRefMark* pMark1, const SwFormatRefMark* pMark2) -> bool {
const SwTextRefMark* pTextRefMark1 = pMark1->GetTextRefMark();
const SwTextRefMark* pTextRefMark2 = pMark2->GetTextRefMark();
SwPosition aPos1(pTextRefMark1->GetTextNode(), pTextRefMark1->GetStart());
SwPosition aPos2(pTextRefMark2->GetTextNode(), pTextRefMark2->GetStart());
return aPos1 < aPos2;
});
for (const auto& pRefMark : aRefMarks)
{
if (!pRefMark->GetRefName().toString().startsWith(aNamePrefix))
{
continue;
}
auto aProperty = rJsonWriter.startStruct();
rJsonWriter.put("name", pRefMark->GetRefName().toString());
}
}
/// Implements getCommandValues(".uno:Layout").
void GetLayout(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell)
{
rJsonWriter.put("commandName", ".uno:Layout");
auto aCommandValues = rJsonWriter.startNode("commandValues");
auto aPages = rJsonWriter.startArray("pages");
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
SwRootFrame* pLayout = pWrtShell->GetLayout();
for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
{
auto pPage = pFrame->DynCastPageFrame();
auto aPage = rJsonWriter.startStruct();
rJsonWriter.put("isInvalidContent", pPage->IsInvalidContent());
}
}
/// Implements getCommandValues(".uno:Field").
///
/// Parameters:
///
/// - typeName: field type condition to not return all fields
/// - namePrefix: field name prefix to not return all fields
void GetField(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aTypeName;
{
auto it = rArguments.find(u"typeName"_ustr);
if (it != rArguments.end())
{
aTypeName = it->second;
}
}
// See SwFieldTypeFromString().
if (aTypeName != "SetRef")
{
return;
}
OUString aNamePrefix;
{
auto it = rArguments.find(u"namePrefix"_ustr);
if (it != rArguments.end())
{
aNamePrefix = it->second;
}
}
SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
SwPosition& rCursor = *pWrtShell->GetCursor()->GetPoint();
SwTextNode* pTextNode = rCursor.GetNode().GetTextNode();
std::vector<SwTextAttr*> aAttrs
= pTextNode->GetTextAttrsAt(rCursor.GetContentIndex(), RES_TXTATR_REFMARK);
auto aRefmark = rJsonWriter.startNode("setRef");
if (aAttrs.empty())
{
return;
}
const SwFormatRefMark& rRefmark = aAttrs[0]->GetRefMark();
if (!rRefmark.GetRefName().toString().startsWith(aNamePrefix))
{
return;
}
rJsonWriter.put("name", rRefmark.GetRefName().toString());
}
/// Implements getCommandValues(".uno:ExtractDocumentStructures") for content controls
void GetDocStructureContentControls(tools::JsonWriter& rJsonWriter, const SwDocShell* pDocShell)
{
uno::Reference<container::XIndexAccess> xContentControls
= pDocShell->GetBaseModel()->getContentControls();
if (!xContentControls)
return;
int iCCcount = xContentControls->getCount();
for (int i = 0; i < iCCcount; ++i)
{
OString aNodeName("ContentControls.ByIndex."_ostr + OString::number(i));
auto ContentControlNode = rJsonWriter.startNode(aNodeName);
uno::Reference<text::XTextContent> xContentControl;
xContentControls->getByIndex(i) >>= xContentControl;
uno::Reference<text::XText> xContentControlText(xContentControl, uno::UNO_QUERY);
uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY);
sal_Int32 iID = -1;
xContentControlProps->getPropertyValue(UNO_NAME_ID) >>= iID;
rJsonWriter.put("id", iID);
OUString aTag;
xContentControlProps->getPropertyValue(UNO_NAME_TAG) >>= aTag;
rJsonWriter.put("tag", aTag);
OUString aAlias;
xContentControlProps->getPropertyValue(UNO_NAME_ALIAS) >>= aAlias;
rJsonWriter.put("alias", aAlias);
sal_Int32 iType(0);
xContentControlProps->getPropertyValue(UNO_NAME_CONTENT_CONTROL_TYPE) >>= iType;
SwContentControlType aType = static_cast<SwContentControlType>(iType);
bool bShowingPlaceHolder = false;
xContentControlProps->getPropertyValue(UNO_NAME_SHOWING_PLACE_HOLDER)
>>= bShowingPlaceHolder;
OUString aContent;
if (!bShowingPlaceHolder)
{
aContent = xContentControlText->getString();
}
rJsonWriter.put("content", aContent);
switch (aType)
{
case SwContentControlType::RICH_TEXT:
{
rJsonWriter.put("type", "rich-text");
}
break;
case SwContentControlType::CHECKBOX:
{
rJsonWriter.put("type", "checkbox");
bool bchecked = false;
xContentControlProps->getPropertyValue(UNO_NAME_CHECKED) >>= bchecked;
rJsonWriter.put(UNO_NAME_CHECKED, OUString::boolean(bchecked));
}
break;
case SwContentControlType::DROP_DOWN_LIST:
{
rJsonWriter.put("type", "drop-down-list");
// we could list its elements if we want
}
break;
case SwContentControlType::PICTURE:
{
rJsonWriter.put("type", "picture");
}
break;
case SwContentControlType::DATE:
{
rJsonWriter.put("type", "date");
OUString aDateFormat;
xContentControlProps->getPropertyValue(UNO_NAME_DATE_FORMAT) >>= aDateFormat;
rJsonWriter.put(UNO_NAME_DATE_FORMAT, aDateFormat);
OUString aDateLanguage;
xContentControlProps->getPropertyValue(UNO_NAME_DATE_LANGUAGE) >>= aDateLanguage;
rJsonWriter.put(UNO_NAME_DATE_LANGUAGE, aDateLanguage);
OUString aCurrentDate;
xContentControlProps->getPropertyValue(UNO_NAME_CURRENT_DATE) >>= aCurrentDate;
rJsonWriter.put(UNO_NAME_CURRENT_DATE, aCurrentDate);
}
break;
case SwContentControlType::PLAIN_TEXT:
{
rJsonWriter.put("type", "plain-text");
}
break;
case SwContentControlType::COMBO_BOX:
{
rJsonWriter.put("type", "combo-box");
// we could list its elements if we want
}
break;
default:
//it should never happen
rJsonWriter.put("type", "no type?");
}
}
}
/// Implements getCommandValues(".uno:ExtractDocumentStructures") for charts
void GetDocStructureCharts(tools::JsonWriter& rJsonWriter, const SwDocShell* pDocShell)
{
uno::Reference<container::XIndexAccess> xEmbeddeds(
pDocShell->GetBaseModel()->getEmbeddedObjects(), uno::UNO_QUERY);
if (!xEmbeddeds)
return;
sal_Int32 nEOcount = xEmbeddeds->getCount();
for (int i = 0; i < nEOcount; ++i)
{
uno::Reference<beans::XPropertySet> xShapeProps(xEmbeddeds->getByIndex(i), uno::UNO_QUERY);
if (!xShapeProps.is())
continue;
uno::Reference<frame::XModel> xDocModel;
xShapeProps->getPropertyValue(u"Model"_ustr) >>= xDocModel;
if (!xDocModel.is())
continue;
uno::Reference<chart2::XChartDocument> xChartDoc(xDocModel, uno::UNO_QUERY);
if (!xChartDoc.is())
continue;
uno::Reference<chart2::data::XDataProvider> xDataProvider(xChartDoc->getDataProvider());
if (!xDataProvider.is())
continue;
uno::Reference<chart::XChartDataArray> xDataArray(xChartDoc->getDataProvider(),
uno::UNO_QUERY);
if (!xDataArray.is())
continue;
uno::Reference<chart2::XDiagram> xDiagram = xChartDoc->getFirstDiagram();
if (!xDiagram.is())
continue;
//we have the chart Data now, we can start to extract it
OString aNodeName("Charts.ByEmbedIndex."_ostr + OString::number(i));
auto aChartNode = rJsonWriter.startNode(aNodeName);
//get the object name
uno::Reference<container::XNamed> xNamedShape(xEmbeddeds->getByIndex(i), uno::UNO_QUERY);
if (xNamedShape.is())
{
OUString aName;
aName = xNamedShape->getName();
rJsonWriter.put("name", aName);
}
//get the chart title, if there is one
uno::Reference<chart2::XTitled> xTitled(xChartDoc, uno::UNO_QUERY_THROW);
if (xTitled.is())
{
uno::Reference<chart2::XTitle> xTitle = xTitled->getTitleObject();
if (xTitle.is())
{
OUStringBuffer aTitle;
const uno::Sequence<uno::Reference<chart2::XFormattedString>> aFSSeq
= xTitle->getText();
for (auto const& fs : aFSSeq)
aTitle.append(fs->getString());
rJsonWriter.put("title", aTitle.toString());
}
}
//get the chart subtitle, if there is one
uno::Reference<chart2::XTitled> xSubTitled(xDiagram, uno::UNO_QUERY_THROW);
if (xSubTitled.is())
{
uno::Reference<chart2::XTitle> xSubTitle = xSubTitled->getTitleObject();
if (xSubTitle.is())
{
OUStringBuffer aSubTitle;
const uno::Sequence<uno::Reference<chart2::XFormattedString>> aFSSeq
= xSubTitle->getText();
for (auto const& fs : aFSSeq)
aSubTitle.append(fs->getString());
rJsonWriter.put("subtitle", aSubTitle.makeStringAndClear());
}
}
{
uno::Sequence<OUString> aRowDesc = xDataArray->getRowDescriptions();
auto aRowDescNode = rJsonWriter.startArray("RowDescriptions");
for (int j = 0; j < aRowDesc.getLength(); j++)
{
rJsonWriter.putSimpleValue(aRowDesc[j]);
}
}
{
uno::Sequence<OUString> aColDesc = xDataArray->getColumnDescriptions();
auto aColDescNode = rJsonWriter.startArray("ColumnDescriptions");
for (int j = 0; j < aColDesc.getLength(); j++)
{
rJsonWriter.putSimpleValue(aColDesc[j]);
}
}
{
uno::Sequence<uno::Sequence<double>> aData = xDataArray->getData();
auto aDataValuesNode = rJsonWriter.startArray("DataValues");
for (int j = 0; j < aData.getLength(); j++)
{
auto aRowNode = rJsonWriter.startAnonArray();
for (int k = 0; k < aData[j].getLength(); k++)
{
rJsonWriter.putSimpleValue(OUString::number(aData[j][k]));
}
}
}
}
}
/// Implements getCommandValues(".uno:ExtractDocumentStructures") for document properties
void GetDocStructureDocProps(tools::JsonWriter& rJsonWriter, const SwDocShell* pDocShell)
{
uno::Reference<document::XDocumentProperties2> xDocProps(
pDocShell->GetBaseModel()->getDocumentProperties(), uno::UNO_QUERY);
if (!xDocProps.is())
return;
auto aDocPropsNode = rJsonWriter.startNode("DocumentProperties");
// StringBuffer for converting DateTimes to String
OUStringBuffer aDateBuf(32);
//Properties from XDocumentProperties
OUString aAuthor = xDocProps->getAuthor();
rJsonWriter.put("Author", aAuthor);
OUString aGenerator = xDocProps->getGenerator();
rJsonWriter.put("Generator", aGenerator);
util::DateTime aCreationDate = xDocProps->getCreationDate();
sax::Converter::convertDateTime(aDateBuf, aCreationDate, nullptr, true);
rJsonWriter.put("CreationDate", aDateBuf.makeStringAndClear());
OUString aTitle = xDocProps->getTitle();
rJsonWriter.put("Title", aTitle);
OUString aSubject = xDocProps->getSubject();
rJsonWriter.put("Subject", aSubject);
OUString aDescription = xDocProps->getDescription();
rJsonWriter.put("Description", aDescription);
uno::Sequence<OUString> aKeywords = xDocProps->getKeywords();
{
auto aKeywordsNode = rJsonWriter.startArray("Keywords");
for (int i = 0; i < aKeywords.getLength(); i++)
{
rJsonWriter.putSimpleValue(aKeywords[i]);
}
}
lang::Locale aLanguage = xDocProps->getLanguage();
OUString aLanguageStr(LanguageTag::convertToBcp47(aLanguage, false));
rJsonWriter.put("Language", aLanguageStr);
OUString aModifiedBy = xDocProps->getModifiedBy();
rJsonWriter.put("ModifiedBy", aModifiedBy);
util::DateTime aModificationDate = xDocProps->getModificationDate();
sax::Converter::convertDateTime(aDateBuf, aModificationDate, nullptr, true);
rJsonWriter.put("ModificationDate", aDateBuf.makeStringAndClear());
OUString aPrintedBy = xDocProps->getPrintedBy();
rJsonWriter.put("PrintedBy", aPrintedBy);
util::DateTime aPrintDate = xDocProps->getPrintDate();
sax::Converter::convertDateTime(aDateBuf, aPrintDate, nullptr, true);
rJsonWriter.put("PrintDate", aDateBuf.makeStringAndClear());
OUString aTemplateName = xDocProps->getTemplateName();
rJsonWriter.put("TemplateName", aTemplateName);
OUString aTemplateURL = xDocProps->getTemplateURL();
rJsonWriter.put("TemplateURL", aTemplateURL);
util::DateTime aTemplateDate = xDocProps->getTemplateDate();
sax::Converter::convertDateTime(aDateBuf, aTemplateDate, nullptr, true);
rJsonWriter.put("TemplateDate", aDateBuf.makeStringAndClear());
OUString aAutoloadURL = xDocProps->getAutoloadURL();
rJsonWriter.put("AutoloadURL", aAutoloadURL);
sal_Int32 aAutoloadSecs = xDocProps->getAutoloadSecs();
rJsonWriter.put("AutoloadSecs", aAutoloadSecs);
OUString aDefaultTarget = xDocProps->getDefaultTarget();
rJsonWriter.put("DefaultTarget", aDefaultTarget);
uno::Sequence<beans::NamedValue> aDocumentStatistics = xDocProps->getDocumentStatistics();
{
auto aDocumentStatisticsNode = rJsonWriter.startNode("DocumentStatistics");
for (int i = 0; i < aDocumentStatistics.getLength(); i++)
{
// Todo check: do all stats are integer numbers?
sal_Int32 nValue = 0;
aDocumentStatistics[i].Value >>= nValue;
std::string aStr(OUStringToOString(aDocumentStatistics[i].Name, RTL_TEXTENCODING_UTF8));
rJsonWriter.put(aStr, nValue);
}
}
sal_Int16 aEditingCycles = xDocProps->getEditingCycles();
rJsonWriter.put("EditingCycles", aEditingCycles);
sal_Int32 aEditingDuration = xDocProps->getEditingDuration();
rJsonWriter.put("EditingDuration", aEditingDuration);
//Properties from XDocumentProperties2
uno::Sequence<OUString> aContributor = xDocProps->getContributor();
{
auto aContributorNode = rJsonWriter.startArray("Contributor");
for (int i = 0; i < aContributor.getLength(); i++)
{
rJsonWriter.putSimpleValue(aContributor[i]);
}
}
OUString aCoverage = xDocProps->getCoverage();
rJsonWriter.put("Coverage", aCoverage);
OUString aIdentifier = xDocProps->getIdentifier();
rJsonWriter.put("Identifier", aIdentifier);
uno::Sequence<OUString> aPublisher = xDocProps->getPublisher();
{
auto aPublisherNode = rJsonWriter.startArray("Publisher");
for (int i = 0; i < aPublisher.getLength(); i++)
{
rJsonWriter.putSimpleValue(aPublisher[i]);
}
}
uno::Sequence<OUString> aRelation = xDocProps->getRelation();
{
auto aRelationNode = rJsonWriter.startArray("Relation");
for (int i = 0; i < aRelation.getLength(); i++)
{
rJsonWriter.putSimpleValue(aRelation[i]);
}
}
OUString aRights = xDocProps->getRights();
rJsonWriter.put("Rights", aRights);
OUString aSource = xDocProps->getSource();
rJsonWriter.put("Source", aSource);
OUString aType = xDocProps->getType();
rJsonWriter.put("Type", aType);
// PropertySet -> JSON
css::uno::Reference<css::beans::XPropertyContainer> aUserDefinedProperties
= xDocProps->getUserDefinedProperties();
uno::Reference<beans::XPropertySet> aUserDefinedPropertySet(aUserDefinedProperties,
uno::UNO_QUERY);
if (aUserDefinedPropertySet.is())
{
auto aRelationNode = rJsonWriter.startNode("UserDefinedProperties");
const uno::Sequence<beans::Property> aProperties
= aUserDefinedPropertySet->getPropertySetInfo()->getProperties();
for (const beans::Property& rProperty : aProperties)
{
const OUString& rKey = rProperty.Name;
auto aNode = rJsonWriter.startNode(OUStringToOString(rKey, RTL_TEXTENCODING_UTF8));
uno::Any aValue = aUserDefinedPropertySet->getPropertyValue(rKey);
OUString aAnyType = aValue.getValueTypeName();
rJsonWriter.put("type", aAnyType);
if (aAnyType == "boolean")
rJsonWriter.put("value", aValue.get<bool>());
else if (aAnyType == "double")
rJsonWriter.put("value", aValue.get<double>());
else if (aAnyType == "float")
rJsonWriter.put("value", aValue.get<float>());
else if (aAnyType == "long")
rJsonWriter.put("value", aValue.get<sal_Int32>());
else if (aAnyType == "short")
rJsonWriter.put("value", aValue.get<sal_Int16>());
else if (aValue.has<OUString>())
rJsonWriter.put("value", aValue.get<OUString>());
else if (aValue.has<sal_uInt64>())
rJsonWriter.put("value", aValue.get<sal_Int64>());
else
{
// Todo: some more types should be supported..
// AddProperty allow these 13 types:
// "com.sun.star.util.Date", "DateTime", "DateTimeWithTimezone",
// "DateWithTimezone", "Duration", "Time"
// typelib_TypeClass_BOOLEAN, typelib_TypeClass_DOUBLE, typelib_TypeClass_FLOAT
// typelib_TypeClass_HYPER, typelib_TypeClass_LONG, typelib_TypeClass_SHORT,
// typelib_TypeClass_STRING
}
}
}
}
// This class temporarily hides / shows redlines in the document, based on timestamp: when a redline
// is newer than the date, it is "hidden" (the text looks as if that redline were rejected); and
// otherwise the redline is "shown" (the text looks as if the redline is accepted). This allows to
// obtain textBefore / textAfter context attributes for a given redline as it was when the redline
// was created. The state of redlines is restored to original in dtor.
class HideNewerShowOlder
{
public:
HideNewerShowOlder(DateTime limit, const SwRedlineTable& rTable)
: m_rTable(rTable)
, m_aRedlineShowStateRestore(collectRestoreData(m_rTable))
{
for (auto pRedline : m_rTable)
{
const auto& data = pRedline->GetRedlineData();
if (data.GetType() != RedlineType::Insert && data.GetType() != RedlineType::Delete)
continue;
bool hide;
if (limit < data.GetTimeStamp())
hide = data.GetType() == RedlineType::Insert;
else // not later
hide = data.GetType() == RedlineType::Delete;
if (hide)
Hide(pRedline, m_rTable);
else
Show(pRedline, m_rTable);
}
}
void ImplDestroy()
{
// I assume, that only the redlines explicitly handled in ctor would change their visible
// state; so here, only Insert / Delete redlines will be handled.
for (auto[pRedline, visible] : m_aRedlineShowStateRestore)
{
if (visible)
Show(pRedline, m_rTable);
else
Hide(pRedline, m_rTable);
}
}
~HideNewerShowOlder() { suppress_fun_call_w_exception(ImplDestroy()); }
private:
static std::unordered_map<SwRangeRedline*, bool>
collectRestoreData(const SwRedlineTable& rTable)
{
std::unordered_map<SwRangeRedline*, bool> aRedlineShowStateRestore;
for (auto pRedline : rTable)
aRedlineShowStateRestore[pRedline] = pRedline->IsVisible();
return aRedlineShowStateRestore;
}
static void Show(SwRangeRedline* pRedline, const SwRedlineTable& rTable)
{
if (pRedline->IsVisible())
return;
switch (pRedline->GetType())
{
case RedlineType::Insert:
case RedlineType::Delete:
pRedline->Show(0, rTable.GetPos(pRedline), true);
pRedline->Show(1, rTable.GetPos(pRedline), true);
break;
default:
assert(!"Trying to show a redline that is not expected to change visibility here");
}
}
static void Hide(SwRangeRedline* pRedline, const SwRedlineTable& rTable)
{
if (!pRedline->IsVisible())
return;
switch (pRedline->GetType())
{
case RedlineType::Insert:
pRedline->ShowOriginal(0, rTable.GetPos(pRedline));
pRedline->ShowOriginal(1, rTable.GetPos(pRedline));
break;
case RedlineType::Delete:
pRedline->Hide(0, rTable.GetPos(pRedline));
pRedline->Hide(1, rTable.GetPos(pRedline));
break;
default:
assert(!"Trying to hide a redline that is not expected to change visibility here");
}
}
const SwRedlineTable& m_rTable;
std::unordered_map<SwRangeRedline*, bool> m_aRedlineShowStateRestore;
};
/// Implements getCommandValues(".uno:ExtractDocumentStructures") for redlines
void GetDocStructureTrackChanges(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
std::u16string_view filterArguments)
{
// filter arguments are separated from the filter name by comma, and are name:value pairs
// separated by commas
if (!filterArguments.empty() && !filterArguments.starts_with(u","))
return; // not a correct filter
sal_Int16 nContextLen = 200;
bool bPageNumbers = false;
for (size_t paramPos = 1; paramPos < filterArguments.size();)
{
std::u16string_view param = o3tl::getToken(filterArguments, u',', paramPos);
sal_Int32 nIndex = 0;
std::u16string_view token = o3tl::trim(o3tl::getToken(param, 0, u':', nIndex));
std::u16string_view value
= nIndex > 0 ? o3tl::trim(param.substr(nIndex)) : std::u16string_view{};
if (token == u"contextLen")
{
if (!value.empty())
nContextLen = o3tl::toInt32(value);
}
else if (token == u"startPageNumber")
{
bPageNumbers = value == u"true";
}
// else unknown filter argument (maybe from a newer API?) - ignore
}
SwDoc& rDoc = *pDocShell->GetDoc();
const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
uno::Reference<text::XTextCursor> xTextViewCursor;
uno::Reference<text::XPageCursor> xTextPageCursor;
if (bPageNumbers)
{
if (auto xSupplier = pDocShell->GetController().query<text::XTextViewCursorSupplier>())
{
xTextViewCursor.set(xSupplier->getViewCursor());
xTextPageCursor.set(xTextViewCursor, uno::UNO_QUERY);
}
}
for (size_t i = 0; i < rTable.size(); ++i)
{
rtl::Reference<SwXRedline> pSwXRedline(new SwXRedline(*rTable[i]));
auto TrackChangesNode
= rJsonWriter.startNode(Concat2View("TrackChanges.ByIndex." + OString::number(i)));
PropertyExtractor extractor{ pSwXRedline, rJsonWriter };
extractor.extract<OUString>(UNO_NAME_REDLINE_TYPE, "type");
extractor.extract<css::util::DateTime>(UNO_NAME_REDLINE_DATE_TIME, "dateTime");
extractor.extract<OUString>(UNO_NAME_REDLINE_AUTHOR, "author");
extractor.extract<OUString>(UNO_NAME_REDLINE_DESCRIPTION, "description");
extractor.extract<OUString>(UNO_NAME_REDLINE_COMMENT, "comment");
{
// Set the text into a state according to current redline's timestamp: all older changes
// are shows as if accepted, all newer are shown as if rejected.
HideNewerShowOlder prepare(pSwXRedline->GetRedline()->GetTimeStamp(), rTable);
auto xStart = pSwXRedline->getPropertyValue(UNO_NAME_REDLINE_START)
.query<css::text::XTextRange>();
auto xEnd = pSwXRedline->getPropertyValue(UNO_NAME_REDLINE_END)
.query<css::text::XTextRange>();
if (xStart)
{
auto xCursor = xStart->getText()->createTextCursorByRange(xStart);
xCursor->goLeft(nContextLen, /*bExpand*/ true);
if (xTextPageCursor)
{
try
{
xTextViewCursor->gotoRange(xStart, false);
rJsonWriter.put("startPageNumber", xTextPageCursor->getPage());
}
catch (const uno::Exception&)
{
DBG_UNHANDLED_EXCEPTION("sw.ui");
}
}
rJsonWriter.put("textBefore", xCursor->getString());
}
if (xEnd)
{
auto xCursor = xEnd->getText()->createTextCursorByRange(xEnd);
xCursor->goRight(nContextLen, /*bExpand*/ true);
rJsonWriter.put("textAfter", xCursor->getString());
}
OUString changeText;
if (xStart && xEnd)
{
// Read the added / formatted text from the main XText
auto xCursor = xStart->getText()->createTextCursorByRange(xStart);
xCursor->gotoRange(xEnd, /*bExpand*/ true);
changeText = xCursor->getString();
}
if (changeText.isEmpty())
{
// It is unlikely that we get here: the change text will be obtained above,
// even for deletion change
if (auto xRedlineText = pSwXRedline->getPropertyValue(UNO_NAME_REDLINE_TEXT)
.query<css::text::XText>())
changeText = xRedlineText->getString();
}
rJsonWriter.put("textChanged", changeText); // write unconditionally
}
// UNO_NAME_REDLINE_IDENTIFIER: OUString (the value of a pointer, not persistent)
// UNO_NAME_REDLINE_MOVED_ID: sal_uInt32; 0 == not moved, 1 == moved, but don't have its pair, 2+ == unique ID
// UNO_NAME_REDLINE_SUCCESSOR_DATA: uno::Sequence<beans::PropertyValue>
// UNO_NAME_IS_IN_HEADER_FOOTER: bool
// UNO_NAME_MERGE_LAST_PARA: bool
}
}
/// Implements getCommandValues(".uno:ExtractDocumentStructures").
///
/// Parameters:
///
/// - filter: To filter what document structure types to extract
void GetDocStructure(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
auto commentsNode = rJsonWriter.startNode("DocStructure");
OUString filter;
if (auto it = rArguments.find(u"filter"_ustr); it != rArguments.end())
filter = it->second;
if (filter.isEmpty() || filter == "charts")
GetDocStructureCharts(rJsonWriter, pDocShell);
if (filter.isEmpty() || filter == "contentcontrol")
GetDocStructureContentControls(rJsonWriter, pDocShell);
if (filter.isEmpty() || filter == "docprops")
GetDocStructureDocProps(rJsonWriter, pDocShell);
if (std::u16string_view rest; filter.isEmpty() || filter.startsWith("trackchanges", &rest))
GetDocStructureTrackChanges(rJsonWriter, pDocShell, o3tl::trim(rest));
}
/// Implements getCommandValues(".uno:Sections").
///
/// Parameters:
///
/// - namePrefix: field name prefix to not return all sections
void GetSections(tools::JsonWriter& rJsonWriter, SwDocShell* pDocShell,
const std::map<OUString, OUString>& rArguments)
{
OUString aNamePrefix;
{
auto it = rArguments.find(u"namePrefix"_ustr);
if (it != rArguments.end())
{
aNamePrefix = it->second;
}
}
SwDoc* pDoc = pDocShell->GetDoc();
auto aBookmarks = rJsonWriter.startArray("sections");
for (const auto& pSection : pDoc->GetSections())
{
if (!pSection->GetName().toString().startsWith(aNamePrefix))
{
continue;
}
auto aProperty = rJsonWriter.startStruct();
rJsonWriter.put("name", pSection->GetName().toString());
}
}
}
bool SwXTextDocument::supportsCommand(std::u16string_view rCommand)
{
static const std::initializer_list<std::u16string_view> vForward
= { u"TextFormFields", u"TextFormField", u"SetDocumentProperties",
u"Bookmarks", u"Fields", u"Sections",
u"Bookmark", u"Field", u"Layout" };
return std::find(vForward.begin(), vForward.end(), rCommand) != vForward.end();
}
int SwXTextDocument::getEditMode()
{
SwViewShell* pViewShell = m_pDocShell->GetWrtShell();
if (!pViewShell)
{
return 0;
}
SfxViewShell* pView = pViewShell->GetSfxViewShell();
if (!pView)
{
return 0;
}
return pView->getEditMode();
}
void SwXTextDocument::setEditMode(int nEditMode)
{
auto eMode = static_cast<SwRedlineRenderMode>(nEditMode);
SwViewShell* pViewShell = m_pDocShell->GetWrtShell();
if (!pViewShell)
{
return;
}
SwViewOption aOpt(*pViewShell->GetViewOptions());
if (eMode != aOpt.GetRedlineRenderMode())
{
aOpt.SetRedlineRenderMode(eMode);
pViewShell->ApplyViewOptions(aOpt);
}
}
OUString SwXTextDocument::getPartInfo(int /*nPart*/)
{
tools::JsonWriter jsonWriter;
jsonWriter.put("mode", getEditMode());
return OUString::fromUtf8(jsonWriter.finishAndGetAsOString());
}
void SwXTextDocument::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand)
{
using namespace std::string_view_literals;
std::map<OUString, OUString> aMap
= SfxLokHelper::parseCommandParameters(OUString::fromUtf8(rCommand));
if (o3tl::starts_with(rCommand, ".uno:TextFormFields"sv))
{
GetTextFormFields(rJsonWriter, m_pDocShell, aMap);
}
if (o3tl::starts_with(rCommand, ".uno:TextFormField"sv))
{
GetTextFormField(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:SetDocumentProperties"sv))
{
GetDocumentProperties(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:Bookmarks"sv))
{
GetBookmarks(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:Fields"sv))
{
GetFields(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:Sections"sv))
{
GetSections(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:Bookmark"sv))
{
GetBookmark(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:Field"sv))
{
GetField(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:ExtractDocumentStructure"sv))
{
GetDocStructure(rJsonWriter, m_pDocShell, aMap);
}
else if (o3tl::starts_with(rCommand, ".uno:Layout"sv))
{
GetLayout(rJsonWriter, m_pDocShell);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression '!bShowingPlaceHolder' is always true.
↑ V547 Expression is always false.
↑ V547 Expression is always false.
↑ V785 Constant expression in switch statement.