README.yrs                                   |   46 
 RepositoryExternal.mk                        |   17 
 config_host.mk.in                            |    1 
 configure.ac                                 |    6 
 editeng/CppunitTest_editeng_core.mk          |    1 
 editeng/CppunitTest_editeng_editeng.mk       |    1 
 editeng/Library_editeng.mk                   |    1 
 editeng/inc/editdoc.hxx                      |   30 
 editeng/source/editeng/editdoc.cxx           | 2918 ++++++++++++++++++++++++---
 editeng/source/editeng/impedit.hxx           |    3 
 editeng/source/editeng/impedit5.cxx          |    6 
 include/editeng/editview.hxx                 |   14 
 include/editeng/yrs.hxx                      |   69 
 sfx2/Library_sfx.mk                          |    4 
 sfx2/source/view/frmload.cxx                 |   43 
 sw/Library_sw.mk                             |    1 
 sw/inc/IDocumentState.hxx                    |   23 
 sw/inc/PostItMgr.hxx                         |    3 
 sw/source/core/doc/DocumentStateManager.cxx  | 1103 ++++++++++
 sw/source/core/inc/DocumentStateManager.hxx  |   33 
 sw/source/uibase/app/docsh.cxx               |   16 
 sw/source/uibase/docvw/AnnotationWin.cxx     |   14 
 sw/source/uibase/docvw/PostItMgr.cxx         |   11 
 sw/source/uibase/docvw/SidebarTxtControl.cxx |   11 
 sw/source/uibase/docvw/SidebarTxtControl.hxx |    3 
 sw/source/uibase/fldui/fldmgr.cxx            |   22 
 sw/source/uibase/inc/wrtsh.hxx               |    5 
 sw/source/uibase/uiview/view.cxx             |    3 
 sw/source/uibase/uno/unotxdoc.cxx            |   11 
 sw/source/uibase/wrtsh/wrtsh2.cxx            |   15 
 30 files changed, 4182 insertions(+), 252 deletions(-)

New commits:
commit 8f8034177b9efcfaa52b3dc0980aec58e75fca5b
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Fri Oct 25 15:02:34 2024 +0200
Commit:     Michael Stahl <michael.st...@allotropia.de>
CommitDate: Wed May 7 13:36:01 2025 +0200

    LOCRDT editeng,sfx2,sw: experimental yrs collab
    
    This can be enabled in configure via --with-yrs=...; see also README.yrs
    
    Currently this is hardcoded to run over a local named pipe.
    
    Everything related to editengine is implemented in EditDoc and made
    accessible via its wrapper classes; everything related to communication
    and accessing sw's comment is implemented in sw::DocumentStateManager.
    
    The acceptor starts in SwView::SwView(), once SwPostItMgr exists.
    
    There is a hack in SfxFrameLoader_Impl::load() to fetch the document
    that the accepting soffice has loaded, and load it in the connecting
    soffice as well.
    
    Change-Id: I89476b5864b70f479bcf15989374c1c65b5da9ea
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175652
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>

diff --git a/README.yrs b/README.yrs
new file mode 100644
index 000000000000..55a99e2a3de0
--- /dev/null
+++ b/README.yrs
@@ -0,0 +1,46 @@
+
+## Experimental Writer comments editing collaboration with yrs
+
+### How to build
+
+First, build yrs C FFI bindings:
+
+```
+ git clone https://github.com/y-crdt/y-crdt.git
+ cd y-crdt
+ git checkout v0.23.1
+ cargo build -p yffi
+```
+
+Then, put the yrs build directory in autogen.input:
+
+`--with-yrs=/path/to/y-crdt`
+
+### How to run
+
+To prevent crashes at runtime, set the environment variable
+EDIT_COMMENT_IN_READONLY_MODE=1 and open documents in read-only mode: only
+inserting/deleting comments, and editing inside comments will be enabled.
+
+Currently, communication happens over a hard-coded pipe:
+
+* start an soffice with YRSACCEPT=1 load a Writer document and it will listen
+  and block until connect
+  (you can also create a new Writer document but that will be boring if all
+  you can do is insert comments into empty doc)
+
+* start another soffice with a different user profile, create new Writer
+  document, and it will connect and load the document from the other side
+
+All sorts of paragraph and character formattings should work inside comments.
+
+Inserting hyperlinks also works, although sadly i wasn't able to figure out
+how to enable the menu items in read-only mode, so it only works in editable
+mode.
+
+Undo/Redo doesn't work at all, it's disabled in readonly mode anyway.
+
+Switching to editable mode is also possible, but only comment-related editing
+is synced via yrs, so if other editing operations change the positions of
+comments, a crash will be inevitable.
+
diff --git a/RepositoryExternal.mk b/RepositoryExternal.mk
index f16521c50d7d..9cacf77039d5 100644
--- a/RepositoryExternal.mk
+++ b/RepositoryExternal.mk
@@ -4449,4 +4449,21 @@ $(call gb_LinkTarget_set_include,$(1),\
 endef
 endif
 
+ifneq ($(WITH_YRS),)
+
+define gb_LinkTarget__use_yrs
+$(call gb_LinkTarget_set_include,$(1),\
+       $$(INCLUDE) \
+       -I$(WITH_YRS)/tests-ffi/include \
+)
+$(call gb_LinkTarget_add_defs,$(1),-DYRS)
+$(call gb_LinkTarget_add_libs,$(1),$(WITH_YRS)/target/debug/libyrs.a)
+endef
+
+else
+
+gb_LinkTarget__use_yrs :=
+
+endif
+
 # vim: set noet sw=4 ts=4:
diff --git a/config_host.mk.in b/config_host.mk.in
index 0a4d61f62875..70775a16782e 100644
--- a/config_host.mk.in
+++ b/config_host.mk.in
@@ -784,6 +784,7 @@ export WITH_LOCALES=@WITH_LOCALES@
 export WITH_MYSPELL_DICTS=@WITH_MYSPELL_DICTS@
 export WITH_THEMES=@WITH_THEMES@
 export WITH_WEBDAV=@WITH_WEBDAV@
+WITH_YRS=@WITH_YRS@
 export WORKDIR=@WORKDIR@
 export WORKDIR_FOR_BUILD=@WORKDIR_FOR_BUILD@
 export WPD_CFLAGS=$(gb_SPACE)@WPD_CFLAGS@
diff --git a/configure.ac b/configure.ac
index 7819f4d4ec97..e7059bb611d8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2830,6 +2830,12 @@ AC_ARG_WITH(hamcrest,
          --without-junit disables those tests. Not relevant in the 
--without-java case.]),
 ,with_hamcrest=yes)
 
+AC_ARG_WITH(yrs,
+    AS_HELP_STRING([--with-yrs=<absolute path to yrs build>],
+        [Specifies the built yrs git repo for very experimental experiments.]),
+    WITH_YRS=$withval)
+AC_SUBST(WITH_YRS)
+
 AC_ARG_WITH(perl-home,
     AS_HELP_STRING([--with-perl-home=<abs. path to Perl 5 home>],
         [If you have installed Perl 5 Distribution, on your system, please
diff --git a/editeng/CppunitTest_editeng_core.mk 
b/editeng/CppunitTest_editeng_core.mk
index 46a232d97f38..64f17ae3ba4a 100644
--- a/editeng/CppunitTest_editeng_core.mk
+++ b/editeng/CppunitTest_editeng_core.mk
@@ -53,6 +53,7 @@ $(eval $(call gb_CppunitTest_use_externals,editeng_core,\
        boost_headers \
     icuuc \
        libxml2 \
+       yrs \
 ))
 
 $(eval $(call gb_CppunitTest_set_include,editeng_core,\
diff --git a/editeng/CppunitTest_editeng_editeng.mk 
b/editeng/CppunitTest_editeng_editeng.mk
index 38cbc9543db6..6c2d20917e86 100644
--- a/editeng/CppunitTest_editeng_editeng.mk
+++ b/editeng/CppunitTest_editeng_editeng.mk
@@ -47,6 +47,7 @@ $(eval $(call gb_CppunitTest_use_externals,editeng_editeng,\
     boost_headers \
     icuuc \
     libxml2 \
+       yrs \
 ))
 
 $(eval $(call gb_CppunitTest_set_include,editeng_editeng,\
diff --git a/editeng/Library_editeng.mk b/editeng/Library_editeng.mk
index 36893816b569..cfd4f357270c 100644
--- a/editeng/Library_editeng.mk
+++ b/editeng/Library_editeng.mk
@@ -166,6 +166,7 @@ $(eval $(call gb_Library_use_externals,editeng,\
        icuuc \
        icu_headers \
        libxml2 \
+       yrs \
 ))
 
 # vim: set noet sw=4 ts=4:
diff --git a/editeng/inc/editdoc.hxx b/editeng/inc/editdoc.hxx
index dfb0adec96d2..eb0d79da7e6e 100644
--- a/editeng/inc/editdoc.hxx
+++ b/editeng/inc/editdoc.hxx
@@ -46,6 +46,13 @@
 
 enum class TextRotation;
 
+#if defined(YRS)
+class ImpEditEngine;
+class IYrsTransactionSupplier;
+typedef struct TransactionInner YTransaction;
+typedef struct YTextEvent YTextEvent;
+#endif
+
 
 #define CHARPOSGROW     16
 #define DEFTAB          720
@@ -119,6 +126,19 @@ private:
     bool            mbModified:1;
     bool            mbDisableAttributeExpanding:1;
 
+#if defined(YRS)
+    OString m_CommentId;
+    IYrsTransactionSupplier * m_pYrsSupplier{nullptr};
+public:
+    void SetYrsCommentId(IYrsTransactionSupplier *, OString const& rId);
+    void YrsWriteEEState();
+    void YrsReadEEState(YTransaction *, ImpEditEngine & rIEE);
+    void YrsApplyEEDelta(YTransaction *, YTextEvent const* pEvent, 
ImpEditEngine & rIEE);
+    void YrsSetStyle(sal_Int32 nPara, ::std::u16string_view rStyle);
+    void YrsSetParaAttr(sal_Int32 nPara, SfxPoolItem const& rItem);
+    OString GetYrsCommentId() const;
+#endif
+
 public:
                     EditDoc( SfxItemPool* pItemPool );
                     ~EditDoc();
@@ -139,21 +159,19 @@ public:
     void            CreateDefFont( bool bUseStyles );
     const SvxFont&  GetDefFont() const { return maDefFont; }
 
-    void            SetDefTab(sal_uInt16 nTab)
-    {
-        mnDefTab = nTab ? nTab : DEFTAB;
-    }
+    void            SetDefTab(sal_uInt16 nTab);
 
     sal_uInt16      GetDefTab() const
     {
         return mnDefTab;
     }
 
-    void            SetVertical( bool bVertical )   { mbIsVertical = 
bVertical; }
+    void            SetVertical(bool bVertical);
+
     bool            IsEffectivelyVertical() const;
     bool            IsTopToBottom() const;
     bool            GetVertical() const;
-    void            SetRotation( TextRotation nRotation )   { mnRotation = 
nRotation; }
+    void            SetRotation(TextRotation nRotation);
     TextRotation    GetRotation() const                     { return 
mnRotation; }
 
     void            SetFixedCellHeight( bool bUseFixedCellHeight )
diff --git a/editeng/source/editeng/editdoc.cxx 
b/editeng/source/editeng/editdoc.cxx
index a34ca78d810d..235708d6c93a 100644
--- a/editeng/source/editeng/editdoc.cxx
+++ b/editeng/source/editeng/editdoc.cxx
@@ -42,6 +42,16 @@
 #include <editeng/lrspitem.hxx>
 #include <editeng/ulspitem.hxx>
 #include <editeng/lspcitem.hxx>
+#if defined(YRS)
+#include <editeng/frmdiritem.hxx>
+#include <editeng/hngpnctitem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/justifyitem.hxx>
+#include <editeng/numdef.hxx>
+#include <svl/itemiter.hxx>
+#endif
 
 #include <editdoc.hxx>
 #include <editeng/eerdll.hxx>
@@ -714,283 +724,2625 @@ void EditSelection::Adjust( const EditDoc& rNodes )
     }
 }
 
-EditDoc::EditDoc( SfxItemPool* pPool ) :
-    mnLastCache(0),
-    mpItemPool(pPool ? pPool : new EditEngineItemPool()),
-    mnDefTab(DEFTAB),
-    mbIsVertical(false),
-    mnRotation(TextRotation::NONE),
-    mbIsFixedCellHeight(false),
-    mbModified(false),
-    mbDisableAttributeExpanding(false)
-{
-    // Don't create an empty node, Clear() will be called in EditEngine-CTOR
-};
+#if defined(YRS)
+#include <editeng/yrs.hxx>
 
-EditDoc::~EditDoc()
-{
-    maContents.clear();
-}
+namespace {
 
-void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, 
SvtScriptType nScriptType )
+struct YrsReplayGuard
 {
-    vcl::Font aPrevFont( rFont );
-    rFont.SetAlignment( ALIGN_BASELINE );
-
-    sal_uInt16 nWhich_FontInfo = GetScriptItemId( EE_CHAR_FONTINFO, 
nScriptType );
-    sal_uInt16 nWhich_Language = GetScriptItemId( EE_CHAR_LANGUAGE, 
nScriptType );
-    sal_uInt16 nWhich_FontHeight = GetScriptItemId( EE_CHAR_FONTHEIGHT, 
nScriptType );
-    sal_uInt16 nWhich_Weight = GetScriptItemId( EE_CHAR_WEIGHT, nScriptType );
-    sal_uInt16 nWhich_Italic = GetScriptItemId( EE_CHAR_ITALIC, nScriptType );
+    IYrsTransactionSupplier *const m_pYrsSupplier;
+    IYrsTransactionSupplier::Mode m_Mode;
 
-    if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontInfo ) == 
SfxItemState::SET ) )
+    explicit YrsReplayGuard(IYrsTransactionSupplier *const pYrsSupplier)
+        : m_pYrsSupplier(pYrsSupplier)
     {
-        const SvxFontItem& rFontItem = static_cast<const 
SvxFontItem&>(rSet.Get( nWhich_FontInfo ));
-        rFont.SetFamilyName( rFontItem.GetFamilyName() );
-        rFont.SetFamily( rFontItem.GetFamily() );
-        rFont.SetPitch( rFontItem.GetPitch() );
-        rFont.SetCharSet( rFontItem.GetCharSet() );
+        if (m_pYrsSupplier)
+        {
+            m_Mode = 
m_pYrsSupplier->SetMode(IYrsTransactionSupplier::Mode::Replay);
+        }
     }
-    if ( bSearchInParent || ( rSet.GetItemState( nWhich_Language ) == 
SfxItemState::SET ) )
-        rFont.SetLanguage( static_cast<const SvxLanguageItem&>(rSet.Get( 
nWhich_Language )).GetLanguage() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_COLOR ) == 
SfxItemState::SET ) )
-        rFont.SetColor( rSet.Get( EE_CHAR_COLOR ).GetValue() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_BKGCOLOR ) == 
SfxItemState::SET ) )
+    ~YrsReplayGuard()
     {
-        auto& aColor = rSet.Get( EE_CHAR_BKGCOLOR ).GetValue();
-        rFont.SetTransparent(aColor.IsTransparent());
-        rFont.SetFillColor(aColor);
+        if (m_pYrsSupplier)
+        {
+            m_pYrsSupplier->SetMode(m_Mode);
+        }
     }
-    if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontHeight ) == 
SfxItemState::SET ) )
-        rFont.SetFontSize( Size( rFont.GetFontSize().Width(), 
static_cast<const SvxFontHeightItem&>(rSet.Get( nWhich_FontHeight ) 
).GetHeight() ) );
-    if ( bSearchInParent || ( rSet.GetItemState( nWhich_Weight ) == 
SfxItemState::SET ) )
-        rFont.SetWeight( static_cast<const SvxWeightItem&>(rSet.Get( 
nWhich_Weight )).GetWeight() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_UNDERLINE ) == 
SfxItemState::SET ) )
-        rFont.SetUnderline( rSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OVERLINE ) == 
SfxItemState::SET ) )
-        rFont.SetOverline( rSet.Get( EE_CHAR_OVERLINE ).GetLineStyle() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_STRIKEOUT ) == 
SfxItemState::SET ) )
-        rFont.SetStrikeout( rSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_CASEMAP ) == 
SfxItemState::SET ) )
-        rFont.SetCaseMap( rSet.Get( EE_CHAR_CASEMAP ).GetCaseMap() );
-    if ( bSearchInParent || ( rSet.GetItemState( nWhich_Italic ) == 
SfxItemState::SET ) )
-        rFont.SetItalic( static_cast<const SvxPostureItem&>(rSet.Get( 
nWhich_Italic )).GetPosture() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OUTLINE ) == 
SfxItemState::SET ) )
-        rFont.SetOutline( rSet.Get( EE_CHAR_OUTLINE ).GetValue() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_SHADOW ) == 
SfxItemState::SET ) )
-        rFont.SetShadow( rSet.Get( EE_CHAR_SHADOW ).GetValue() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_ESCAPEMENT ) == 
SfxItemState::SET ) )
-    {
-        const SvxEscapementItem& rEsc = rSet.Get( EE_CHAR_ESCAPEMENT );
-
-        sal_uInt16 const nProp = rEsc.GetProportionalHeight();
-        rFont.SetPropr( static_cast<sal_uInt8>(nProp) );
+};
 
-        short nEsc = rEsc.GetEsc();
-        rFont.SetNonAutoEscapement( nEsc );
-    }
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_PAIRKERNING ) == 
SfxItemState::SET ) )
-        rFont.SetKerning( rSet.Get( EE_CHAR_PAIRKERNING ).GetValue() ? 
FontKerning::FontSpecific : FontKerning::NONE );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_KERNING ) == 
SfxItemState::SET ) )
-        rFont.SetFixKerning( rSet.Get( EE_CHAR_KERNING ).GetValue() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_WLM ) == 
SfxItemState::SET ) )
-        rFont.SetWordLineMode( rSet.Get( EE_CHAR_WLM ).GetValue() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_EMPHASISMARK ) == 
SfxItemState::SET ) )
-        rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK 
).GetEmphasisMark() );
-    if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == 
SfxItemState::SET ) )
-        rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() );
+struct YrsWrite
+{
+    YTransaction *const pTxn;
+    Branch *const pProps;
+    Branch *const pText;
+};
 
-    // Operator == compares the individual members of the font if the impl 
pointer is
-    // not equal. If all members are the same, this assignment makes
-    // sure that both also point to the same internal instance of the font.
-    // To avoid this assignment, you would need to check in
-    // every if statement above whether or not the new value differs from the
-    // old value before making an assignment.
-    if ( rFont == aPrevFont  )
-        rFont = aPrevFont;  // => The same ImpPointer for IsSameInstance
-}
+constexpr char CH_PARA = 0x0d;
 
-void EditDoc::CreateDefFont( bool bUseStyles )
+YrsWrite GetYrsWrite(IYrsTransactionSupplier *const pYrsSupplier,
+    OString const& rId, YTransaction *const pInTxn = nullptr)
 {
-    SfxItemSet aTmpSet(SfxItemSet::makeFixedSfxItemSet<EE_PARA_START, 
EE_CHAR_END>(GetItemPool()));
-    CreateFont(maDefFont, aTmpSet);
-    maDefFont.SetVertical( IsEffectivelyVertical() );
-    maDefFont.SetOrientation( Degree10(IsEffectivelyVertical() ? 
(IsTopToBottom() ? 2700 : 900) : 0) );
-
-    for (std::unique_ptr<ContentNode>& pNode : maContents)
+    if (!pYrsSupplier)
     {
-        pNode->GetCharAttribs().GetDefFont() = maDefFont;
-        if (bUseStyles)
-            pNode->CreateDefFont();
+        return { nullptr, nullptr, nullptr };
     }
-}
-
-bool EditDoc::IsEffectivelyVertical() const
-{
-    return (mbIsVertical && mnRotation == TextRotation::NONE) ||
-        (!mbIsVertical && mnRotation != TextRotation::NONE);
-}
-
-bool EditDoc::IsTopToBottom() const
-{
-    return (mbIsVertical && mnRotation == TextRotation::NONE) ||
-        (!mbIsVertical && mnRotation == TextRotation::TOPTOBOTTOM);
-}
-
-bool EditDoc::GetVertical() const
-{
-    return mbIsVertical;
-}
-
-sal_Int32 EditDoc::GetPos(const ContentNode* pContentNode) const
-{
-    return FastGetPos(maContents, pContentNode, mnLastCache);
-}
-
-const ContentNode* EditDoc::GetObject(sal_Int32 nPos) const
-{
-    return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? 
maContents[nPos].get() : nullptr;
-}
-
-ContentNode* EditDoc::GetObject(sal_Int32 nPos)
-{
-    return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? 
maContents[nPos].get() : nullptr;
-}
-
-void EditDoc::Insert(sal_Int32 nPos, std::unique_ptr<ContentNode> pNode)
-{
-    if (nPos < 0 || nPos == SAL_MAX_INT32)
+    YDoc *const pDoc{pYrsSupplier->GetYDoc()};
+    YTransaction *const pTxn{pInTxn ? pInTxn : 
pYrsSupplier->GetWriteTransaction()};
+    // write is disabled when receiving edits from peers
+    if (!pTxn)
     {
-        SAL_WARN( "editeng", "EditDoc::Insert - overflow pos " << nPos);
-        return;
+        return { nullptr, nullptr, nullptr };
     }
-    maContents.insert(maContents.begin()+nPos, std::move(pNode));
-}
-
-void EditDoc::Remove(sal_Int32 nPos)
-{
-    if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size())
+    assert(pDoc);
+    Branch *const pComments{pYrsSupplier->GetCommentMap()};
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pComment{ymap_get(pComments, pTxn, rId.getStr())};
+    yvalidate(pComment->tag == Y_ARRAY);
+    yvalidate(pComment->len == 1);
+    Branch *const pCommentArray{pComment->value.y_type};
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pProps{yarray_get(pCommentArray, pTxn, 1)};
+    yvalidate(pProps->tag == Y_MAP);
+    yvalidate(pProps->len == 1);
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pText{yarray_get(pCommentArray, pTxn, 2)};
+    yvalidate(pText->tag == Y_TEXT);
+    yvalidate(pText->len == 1);
+    return { pTxn, pProps->value.y_type, pText->value.y_type };
+}
+
+void YrsSetVertical(IYrsTransactionSupplier *const pYrsSupplier,
+    OString const& rCommentId, bool const isVertical)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
     {
-        SAL_WARN( "editeng", "EditDoc::Remove - out of bounds pos " << nPos);
         return;
     }
-    maContents.erase(maContents.begin() + nPos);
+    YInput const input{yinput_bool(isVertical ? Y_TRUE : Y_FALSE)};
+    ymap_insert(yw.pProps, yw.pTxn, "is-vertical", &input);
 }
 
-std::unique_ptr<ContentNode> EditDoc::Release(sal_Int32 nPos)
+void YrsSetRotation(IYrsTransactionSupplier *const pYrsSupplier,
+    OString const& rCommentId, TextRotation const nRotation)
 {
-    if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size())
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
     {
-        SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos);
-        return nullptr;
+        return;
     }
-
-    std::unique_ptr<ContentNode> pNode = std::move(maContents[nPos]);
-    maContents.erase(maContents.begin() + nPos);
-    return pNode;
+    YInput const input{yinput_long(static_cast<int64_t>(nRotation))};
+    ymap_insert(yw.pProps, yw.pTxn, "rotation", &input);
 }
 
-sal_Int32 EditDoc::Count() const
+void YrsSetDefTab(IYrsTransactionSupplier *const pYrsSupplier,
+    OString const& rCommentId, sal_uInt16 const nDefTab)
 {
-    size_t nSize = maContents.size();
-    if (nSize > SAL_MAX_INT32)
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
     {
-        SAL_WARN( "editeng", "EditDoc::Count - overflow " << nSize);
-        return SAL_MAX_INT32;
+        return;
     }
-    return nSize;
-}
-
-OUString EditDoc::GetSepStr( LineEnd eEnd )
-{
-    if ( eEnd == LINEEND_CR )
-        return u" "_ustr; // 0x0d
-    if ( eEnd == LINEEND_LF )
-        return u"
"_ustr; // 0x0a
-    return u"
"_ustr; // 0x0d, 0x0a
+    YInput const input{yinput_long(static_cast<int64_t>(nDefTab))};
+    ymap_insert(yw.pProps, yw.pTxn, "def-tab", &input);
 }
 
-OUString EditDoc::GetText( LineEnd eEnd ) const
+void YrsInsertAttribImplImpl(YrsWrite const& yw, SfxPoolItem const& rItm,
+    uint32_t const nStart, uint32_t const nLen)
 {
-    const sal_Int32 nNodes = Count();
-    if (nNodes == 0)
-        return OUString();
-
-    const OUString aSep = EditDoc::GetSepStr( eEnd );
-    const sal_Int32 nSepSize = aSep.getLength();
-    const sal_Int32 nLen = GetTextLen() + (nNodes - 1)*nSepSize;
-
-    OUStringBuffer aBuffer(nLen + 16); // leave some slack
-
-    for ( sal_Int32 nNode = 0; nNode < nNodes; nNode++ )
+    ::std::vector<YInput> tabStops;
+    ::std::vector<::std::vector<YInput>> tabStopValues;
+    ::std::vector<OString> tempStrings;
+    ::std::vector<YInput> itemArray;
+    ::std::vector<char const*> itemNames;
+    YInput attr;
+    char const* attrName;
+    switch (rItm.Which())
     {
-        if ( nSepSize && nNode>0 )
+        case EE_CHAR_COLOR:
+        case EE_CHAR_BKGCOLOR:
         {
-            aBuffer.append(aSep);
+            sal_uInt32 const nColor{static_cast<SvxColorItem 
const&>(rItm).getColor()};
+            attr = yinput_long(nColor);
+            attrName = rItm.Which() == EE_CHAR_COLOR ? "EE_CHAR_COLOR" : 
"EE_CHAR_BKGCOLOR";
+            break;
         }
-        aBuffer.append(GetParaAsString( GetObject(nNode) ));
-    }
-
-    return aBuffer.makeStringAndClear();
-}
-
-OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const
-{
-    return GetParaAsString( GetObject( nNode ) );
-}
-
-OUString EditDoc::GetParaAsString(
-    const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos)
-{
-    return pNode->GetExpandedText(nStartPos, nEndPos);
-}
-
-EditPaM EditDoc::GetStartPaM() const
-{
-    ContentNode* p = const_cast<ContentNode*>(GetObject(0));
-    return EditPaM(p, 0);
-}
-
-EditPaM EditDoc::GetEndPaM() const
-{
-    ContentNode* pLastNode = const_cast<ContentNode*>(GetObject(Count()-1));
-    return EditPaM( pLastNode, pLastNode->Len() );
-}
-
-sal_Int32 EditDoc::GetTextLen() const
-{
-    sal_Int32 nLength = 0;
-    for (auto const& pContent : maContents)
-    {
-        nLength += pContent->GetExpandedLen();
-    }
-    return nLength;
-}
-
-EditPaM EditDoc::Clear()
-{
-    maContents.clear();
-
-    ContentNode* pNode = new ContentNode(GetItemPool());
-    Insert(0, std::unique_ptr<ContentNode>(pNode));
-
-    CreateDefFont(false);
-
-    SetModified(false);
-
-    return EditPaM( pNode, 0 );
-}
-
-namespace
-{
-struct ClearSpellErrorsHandler
-{
-    void operator() (std::unique_ptr<ContentNode> const & rNode)
-    {
-        rNode->DestroyWrongList();
-    }
-};
-}
+        case EE_CHAR_FONTINFO:
+        case EE_CHAR_FONTINFO_CJK:
+        case EE_CHAR_FONTINFO_CTL:
+        {
+            SvxFontItem const& rItem{static_cast<SvxFontItem const&>(rItm)};
+            tempStrings.reserve(2); // prevent realloc
+            tempStrings.emplace_back(OUStringToOString(rItem.GetFamilyName(), 
RTL_TEXTENCODING_UTF8));
+            itemArray.emplace_back(yinput_string(tempStrings.back().getStr()));
+            itemNames.emplace_back("familyname");
+            tempStrings.emplace_back(OUStringToOString(rItem.GetStyleName(), 
RTL_TEXTENCODING_UTF8));
+            itemArray.emplace_back(yinput_string(tempStrings.back().getStr()));
+            itemNames.emplace_back("style");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetFamily())));
+            itemNames.emplace_back("family");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetPitch())));
+            itemNames.emplace_back("pitch");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetCharSet())));
+            itemNames.emplace_back("charset");
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = rItm.Which() == EE_CHAR_FONTINFO
+                ? "EE_CHAR_FONTINFO"
+                : rItm.Which() == EE_CHAR_FONTINFO_CJK ? 
"EE_CHAR_FONTINFO_CJK" : "EE_CHAR_FONTINFO_CTL";
+            break;
+        }
+        case EE_CHAR_FONTHEIGHT:
+        case EE_CHAR_FONTHEIGHT_CJK:
+        case EE_CHAR_FONTHEIGHT_CTL:
+        {
+            SvxFontHeightItem const& rItem{static_cast<SvxFontHeightItem 
const&>(rItm)};
+            itemNames.emplace_back("height");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetHeight())));
+            itemNames.emplace_back("prop");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetProp())));
+            itemNames.emplace_back("propunit");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetPropUnit())));
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = rItm.Which() == EE_CHAR_FONTHEIGHT
+                ? "EE_CHAR_FONTHEIGHT"
+                : rItm.Which() == EE_CHAR_FONTHEIGHT_CJK ? 
"EE_CHAR_FONTHEIGHT_CJK" : "EE_CHAR_FONTHEIGHT_CTL";
+            break;
+        }
+        case EE_CHAR_FONTWIDTH:
+        {
+            SvxCharScaleWidthItem const& 
rItem{static_cast<SvxCharScaleWidthItem const&>(rItm)};
+            attr = yinput_long(rItem.GetValue());
+            attrName = "EE_CHAR_FONTWIDTH";
+            break;
+        }
+        case EE_CHAR_WEIGHT:
+        case EE_CHAR_WEIGHT_CJK:
+        case EE_CHAR_WEIGHT_CTL:
+        {
+            SvxWeightItem const& rItem{static_cast<SvxWeightItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetWeight()));
+            attrName = rItm.Which() == EE_CHAR_WEIGHT
+                ? "EE_CHAR_WEIGHT"
+                : rItm.Which() == EE_CHAR_WEIGHT_CJK ? "EE_CHAR_WEIGHT_CJK" : 
"EE_CHAR_WEIGHT_CTL";
+            break;
+        }
+        case EE_CHAR_UNDERLINE:
+        case EE_CHAR_OVERLINE:
+        {
+            SvxTextLineItem const& rItem{static_cast<SvxTextLineItem 
const&>(rItm)};
+            
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLineStyle())));
+            itemNames.emplace_back("style");
+            
itemArray.emplace_back(yinput_long(uint64_t(sal_uInt32(rItem.GetColor()))));
+            itemNames.emplace_back("color");
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = rItm.Which() == EE_CHAR_UNDERLINE ? "EE_CHAR_UNDERLINE" 
: "EE_CHAR_OVERLINE";
+            break;
+        }
+        case EE_CHAR_STRIKEOUT:
+        {
+            SvxCrossedOutItem const& rItem{static_cast<SvxCrossedOutItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetStrikeout()));
+            attrName = "EE_CHAR_STRIKEOUT";
+            break;
+        }
+        case EE_CHAR_ITALIC:
+        case EE_CHAR_ITALIC_CJK:
+        case EE_CHAR_ITALIC_CTL:
+        {
+            SvxPostureItem const& rItem{static_cast<SvxPostureItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetPosture()));
+            attrName = rItm.Which() == EE_CHAR_ITALIC
+                ? "EE_CHAR_ITALIC"
+                : rItm.Which() == EE_CHAR_ITALIC_CJK ? "EE_CHAR_ITALIC_CJK" : 
"EE_CHAR_ITALIC_CTL";
+            break;
+        }
+        case EE_CHAR_OUTLINE:
+        {
+            SvxContourItem const& rItem{static_cast<SvxContourItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_CHAR_OUTLINE";
+            break;
+        }
+        case EE_CHAR_SHADOW:
+        {
+            SvxShadowedItem const& rItem{static_cast<SvxShadowedItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_CHAR_SHADOW";
+            break;
+        }
+        case EE_CHAR_ESCAPEMENT:
+        {
+            SvxEscapementItem const& rItem{static_cast<SvxEscapementItem 
const&>(rItm)};
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetEsc())));
+            itemNames.emplace_back("esc");
+            
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetProportionalHeight())));
+            itemNames.emplace_back("prop");
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = "EE_CHAR_ESCAPEMENT";
+            break;
+        }
+        case EE_CHAR_PAIRKERNING:
+        {
+            SvxAutoKernItem const& rItem{static_cast<SvxAutoKernItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_CHAR_PAIRKERNING";
+            break;
+        }
+        case EE_CHAR_KERNING:
+        {
+            SvxKerningItem const& rItem{static_cast<SvxKerningItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_CHAR_KERNING";
+            break;
+        }
+        case EE_CHAR_WLM:
+        {
+            SvxWordLineModeItem const& rItem{static_cast<SvxWordLineModeItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_CHAR_WLM";
+            break;
+        }
+        case EE_CHAR_LANGUAGE:
+        case EE_CHAR_LANGUAGE_CJK:
+        case EE_CHAR_LANGUAGE_CTL:
+        {
+            SvxLanguageItem const& rItem{static_cast<SvxLanguageItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue().get()));
+            attrName = rItm.Which() == EE_CHAR_LANGUAGE
+                ? "EE_CHAR_LANGUAGE"
+                : rItm.Which() == EE_CHAR_LANGUAGE_CJK ? 
"EE_CHAR_LANGUAGE_CJK" : "EE_CHAR_LANGUAGE_CTL";
+            break;
+        }
+        case EE_CHAR_EMPHASISMARK:
+        {
+            SvxEmphasisMarkItem const& rItem{static_cast<SvxEmphasisMarkItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_CHAR_EMPHASISMARK";
+            break;
+        }
+        case EE_CHAR_RELIEF:
+        {
+            SvxCharReliefItem const& rItem{static_cast<SvxCharReliefItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_CHAR_RELIEF";
+            break;
+        }
+        case EE_CHAR_CASEMAP:
+        {
+            SvxCaseMapItem const& rItem{static_cast<SvxCaseMapItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_CHAR_CASEMAP";
+            break;
+        }
+        case EE_PARA_WRITINGDIR:
+        {
+            SvxFrameDirectionItem const& 
rItem{static_cast<SvxFrameDirectionItem const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_PARA_WRITINGDIR";
+            break;
+        }
+        case EE_PARA_HANGINGPUNCTUATION:
+        {
+            SvxHangingPunctuationItem const& 
rItem{static_cast<SvxHangingPunctuationItem const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_PARA_HANGINGPUNCTUATION";
+            break;
+        }
+        case EE_PARA_FORBIDDENRULES:
+        {
+            SvxForbiddenRuleItem const& rItem{static_cast<SvxForbiddenRuleItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_PARA_FORBIDDENRULES";
+            break;
+        }
+        case EE_PARA_ASIANCJKSPACING:
+        {
+            SvxScriptSpaceItem const& rItem{static_cast<SvxScriptSpaceItem 
const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = "EE_PARA_ASIANCJKSPACING";
+            break;
+        }
+//TODO complex, but apparently no way to set this in comment? inline constexpr 
TypedWhichId<SvxNumBulletItem>          EE_PARA_NUMBULLET          
(EE_PARA_START+5);
+        case EE_PARA_HYPHENATE:
+        case EE_PARA_HYPHENATE_NO_CAPS:
+        case EE_PARA_HYPHENATE_NO_LAST_WORD:
+        case EE_PARA_BULLETSTATE:
+        {
+            SfxBoolItem const& rItem{static_cast<SfxBoolItem const&>(rItm)};
+            attr = yinput_bool(rItem.GetValue() ? Y_TRUE : Y_FALSE);
+            attrName = rItm.Which() == EE_PARA_HYPHENATE
+                ? "EE_PARA_HYPHENATE"
+                : rItm.Which() == EE_PARA_HYPHENATE_NO_CAPS
+                    ? "EE_PARA_HYPHENATE_NO_CAPS"
+                    : rItm.Which() == EE_PARA_HYPHENATE_NO_LAST_WORD
+                        ? "EE_PARA_HYPHENATE_NO_LAST_WORD"
+                        : "EE_PARA_BULLETSTATE";
+            break;
+        }
+//TODO no way to set this in comment? inline constexpr 
TypedWhichId<SvxLRSpaceItem>            EE_PARA_OUTLLRSPACE        
(EE_PARA_START+10);
+        case EE_PARA_OUTLLEVEL:
+        {
+            SfxInt16Item const& rItem{static_cast<SfxInt16Item const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_PARA_OUTLLEVEL";
+            break;
+        }
+//TODO complex, but apparently no way to set this in comment? inline constexpr 
TypedWhichId<SvxBulletItem>             EE_PARA_BULLET             
(EE_PARA_START+12);
+        case EE_PARA_LRSPACE:
+        {
+            SvxLRSpaceItem const& rItem{static_cast<SvxLRSpaceItem 
const&>(rItm)};
+            
itemArray.emplace_back(yinput_float(rItem.GetTextFirstLineOffset().m_dValue));
+            itemNames.emplace_back("first-line-offset");
+            
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetTextFirstLineOffset().m_nUnit)));
+            itemNames.emplace_back("first-line-offset-unit");
+            itemArray.emplace_back(yinput_float(rItem.GetLeft().m_dValue));
+            itemNames.emplace_back("left-margin");
+            
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLeft().m_nUnit)));
+            itemNames.emplace_back("left-margin-unit");
+            itemArray.emplace_back(yinput_float(rItem.GetRight().m_dValue));
+            itemNames.emplace_back("right-margin");
+            
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetRight().m_nUnit)));
+            itemNames.emplace_back("right-margin-unit");
+            itemArray.emplace_back(yinput_bool(rItem.IsAutoFirst() ? Y_TRUE : 
Y_FALSE));
+            itemNames.emplace_back("auto-first");
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = "EE_PARA_LRSPACE";
+            break;
+        }
+        case EE_PARA_ULSPACE:
+        {
+            SvxULSpaceItem const& rItem{static_cast<SvxULSpaceItem 
const&>(rItm)};
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetUpper())));
+            itemNames.emplace_back("upper-margin");
+            itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLower())));
+            itemNames.emplace_back("lower-margin");
+            // TODO what does EE support here?
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = "EE_PARA_ULSPACE";
+            break;
+        }
+        case EE_PARA_SBL:
+        {
+            SvxLineSpacingItem const& rItem{static_cast<SvxLineSpacingItem 
const&>(rItm)};
+            switch (rItem.GetLineSpaceRule())
+            {
+                case SvxLineSpaceRule::Auto:
+                    break;
+                case SvxLineSpaceRule::Fix:
+                    
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLineHeight())));
+                    itemNames.emplace_back("line-space-fix");
+                    break;
+                case SvxLineSpaceRule::Min:
+                    
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetLineHeight())));
+                    itemNames.emplace_back("line-space-min");
+                    break;
+            }
+            switch (rItem.GetInterLineSpaceRule())
+            {
+                case SvxInterLineSpaceRule::Off:
+                    break;
+                case SvxInterLineSpaceRule::Prop:
+                    
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetPropLineSpace())));
+                    itemNames.emplace_back("inter-line-space-prop");
+                    break;
+                case SvxInterLineSpaceRule::Fix:
+                    
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetInterLineSpace())));
+                    itemNames.emplace_back("inter-line-space-fix");
+                    break;
+            }
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = "EE_PARA_SBL";
+            break;
+        }
+        case EE_PARA_JUST:
+        {
+            SvxAdjustItem const& rItem{static_cast<SvxAdjustItem 
const&>(rItm)};
+            switch (rItem.GetAdjust())
+            {
+                case SvxAdjust::Left:
+                case SvxAdjust::Right:
+                case SvxAdjust::Center:
+                    attr = yinput_long(uint64_t(rItem.GetAdjust()));
+                    break;
+                case SvxAdjust::Block:
+                    switch (rItem.GetLastBlock())
+                    {
+                        case SvxAdjust::Left:
+                            attr = yinput_long(uint64_t(SvxAdjust::Block));
+                            break;
+                        case SvxAdjust::Center:
+                            attr = yinput_long(uint64_t(5));
+                            break;
+                        case SvxAdjust::Block:
+                            attr = yinput_long(uint64_t(rItem.GetOneWord() == 
SvxAdjust::Block ? 7 : 6));
+                            break;
+                        default:
+                            assert(false);
+                    }
+                    break;
+                default:
+                    assert(false);
+            }
+            attrName = "EE_PARA_JUST";
+            break;
+        }
+        case EE_PARA_TABS:
+        {
+            SvxTabStopItem const& rItem{static_cast<SvxTabStopItem 
const&>(rItm)};
+            itemNames.emplace_back("default-distance");
+            
itemArray.emplace_back(yinput_long(uint64_t(rItem.GetDefaultDistance())));
+            tabStopValues.reserve(rItem.Count()); // prevent realloc
+            for (decltype(rItem.Count()) i{0}; i < rItem.Count(); ++i)
+            {
+                SvxTabStop const& rTab{rItem.At(i)};
+                char const*const names[]{"pos", "adjustment", "decimal", 
"fill"};
+                tabStopValues.emplace_back();
+                
tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetTabPos())));
+                
tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetAdjustment())));
+                
tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetDecimal())));
+                
tabStopValues.back().emplace_back(yinput_long(uint64_t(rTab.GetFill())));
+                
tabStops.emplace_back(yinput_json_map(const_cast<char**>(names), 
tabStopValues.back().data(), 4));
+            }
+            itemNames.emplace_back("tab-stops");
+            itemArray.emplace_back(yinput_json_array(tabStops.data(), 
tabStops.size()));
+            attr = yinput_json_map(const_cast<char**>(itemNames.data()), 
itemArray.data(), itemArray.size());
+            attrName = "EE_PARA_TABS";
+            break;
+        }
+        case EE_PARA_JUST_METHOD:
+        {
+            SvxJustifyMethodItem const& rItem{static_cast<SvxJustifyMethodItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_PARA_JUST_METHOD";
+            break;
+        }
+        case EE_PARA_VER_JUST:
+        {
+            SvxVerJustifyItem const& rItem{static_cast<SvxVerJustifyItem 
const&>(rItm)};
+            attr = yinput_long(uint64_t(rItem.GetValue()));
+            attrName = "EE_PARA_VER_JUST";
+            break;
+        }
+        // these aren't editable?
+//constexpr TypedWhichId<SvXMLAttrContainerItem> EE_CHAR_XMLATTRIBS     
(EE_CHAR_START+27);
+//constexpr TypedWhichId<SfxGrabBagItem>         EE_CHAR_GRABBAG        
(EE_CHAR_START+30);
+
+        default:
+            assert(false);
+    }
+    assert(itemNames.size() == itemArray.size());
+    YInput const attrs{yinput_json_map(const_cast<char**>(&attrName), &attr, 
1)};
+    ytext_format(yw.pText, yw.pTxn, nStart, nLen, &attrs);
+}
+
+void YrsInsertAttribImpl(YrsWrite const& yw, uint32_t const offset, 
EditCharAttrib const*const pAttr)
+{
+    auto const start{offset + pAttr->GetStart()};
+    auto const len{pAttr->GetEnd() - pAttr->GetStart()};
+    YrsInsertAttribImplImpl(yw, *pAttr->GetItem(), start, len);
+}
+
+void YrsInsertFeature(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId,
+    EditDoc const& rDoc, uint32_t const index, EditCharAttrib const*const 
pAttr)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += pAttr->GetStart();
+    char const feature[]{ CH_FEATURE, '
+    switch (pAttr->Which())
+    {
+        case EE_FEATURE_TAB:
+        case EE_FEATURE_LINEBR:
+        {
+            YInput const type{yinput_string(pAttr->Which() == EE_FEATURE_TAB ? 
"tab" : "line")};
+            YInput attrArray[]{ type };
+            char const*const attrNames[]{ "feature" };
+            YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), 
attrArray, 1)};
+            ytext_insert(yw.pText, yw.pTxn, i, feature, &attrs);
+            break;
+        }
+        case EE_FEATURE_FIELD:
+        {
+            SvxURLField const*const pURLField{dynamic_cast<SvxURLField 
const*>(dynamic_cast<SvxFieldItem const*>(pAttr->GetItem())->GetField())};
+            assert(pURLField);
+            YInput const type{yinput_string("url")};
+            // ??? somehow this comes out as Y_JSON_NUM at the other end?
+            YInput const 
format{yinput_long(static_cast<int64_t>(pURLField->GetFormat()))};
+            OString const urlStr{OUStringToOString(pURLField->GetURL(), 
RTL_TEXTENCODING_UTF8)};
+            YInput const url{yinput_string(urlStr.getStr())};
+            OString const 
reprStr{OUStringToOString(pURLField->GetRepresentation(), 
RTL_TEXTENCODING_UTF8)};
+            YInput const representation{yinput_string(reprStr.getStr())};
+            OString const 
targetStr{OUStringToOString(pURLField->GetTargetFrame(), 
RTL_TEXTENCODING_UTF8)};
+            YInput const targetframe{yinput_string(targetStr.getStr())};
+            YInput attrArray[]{ type, format, url, representation, targetframe 
};
+            char const*const attrNames[]{ "feature", "url-format", "url-url", 
"url-representation", "url-targetframe" };
+            // don't use yinput_ymap for this!
+            YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), 
attrArray, 5)};
+            ytext_insert(yw.pText, yw.pTxn, i, feature, &attrs);
+
+            break;
+        }
+        default: // EE_FEATURE_NOTCONV appears unused?
+            assert(false);
+    }
+}
+
+void YrsAddPara(IYrsTransactionSupplier *const pYrsSupplier,
+    OString const& rCommentId, EditDoc const& rDoc, uint32_t const index)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    SAL_DEBUG("YRS YrsAddPara");
+    // need to encode into 1 YText
+    char const para[]{ CH_PARA, '
+    uint32_t i{0};
+    // UTF-16 index should be equal to EditDoc one
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    ContentAttribs const& 
rParaAttribs{rDoc.GetObject(index)->GetContentAttribs()};
+    auto const pStyle{rParaAttribs.GetStyleSheet()};
+    if (pStyle)
+    {
+        OString const styleName{OUStringToOString(pStyle->GetName(), 
RTL_TEXTENCODING_UTF8)};
+        YInput const style{yinput_string(styleName.getStr())};
+        YInput attrArray[]{ style };
+        char const*const attrNames[]{ "para-style" };
+        YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), 
attrArray, 1)};
+        ytext_insert(yw.pText, yw.pTxn, i, para, &attrs);
+    }
+    else
+    {
+        ytext_insert(yw.pText, yw.pTxn, i, para, nullptr);
+    }
+    for (SfxItemIter it{rParaAttribs.GetItems()}; !it.IsAtEnd(); it.NextItem())
+    {
+        YrsInsertAttribImplImpl(yw, *it.GetCurItem(), i, 1);
+    }
+}
+
+void YrsRemovePara(IYrsTransactionSupplier *const pYrsSupplier,
+    OString const& rCommentId, EditDoc const& rDoc, uint32_t const index)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    if (index != 0)
+    {
+        for (auto paras{index}; paras != 0; --paras)
+        {
+            i += rDoc.GetObject(paras-1)->Len() + 1;
+        }
+    }
+    uint32_t const len(rDoc.GetObject(index)->Len() + 1);
+    ytext_remove_range(yw.pText, yw.pTxn, i, len);
+}
+
+void YrsClear(IYrsTransactionSupplier *const pYrsSupplier, OString const& 
rCommentId)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    auto const len{ytext_len(yw.pText, yw.pTxn)};
+    ytext_remove_range(yw.pText, yw.pTxn, 0, len);
+}
+
+void YrsInsertParaBreak(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId,
+    EditDoc const& rDoc, uint32_t const index, uint32_t const content)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    // need to encode into 1 YText
+    char const para[]{ CH_PARA, '
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += content;
+    ContentAttribs const& 
rParaAttribs{rDoc.GetObject(index)->GetContentAttribs()};
+    OString const 
styleName{OUStringToOString(rParaAttribs.GetStyleSheet()->GetName(), 
RTL_TEXTENCODING_UTF8)};
+    YInput const style{yinput_string(styleName.getStr())};
+    YInput attrArray[]{ style };
+    char const*const attrNames[]{ "para-style" };
+    YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), 
attrArray, 1)};
+    ytext_insert(yw.pText, yw.pTxn, i, para, &attrs);
+    for (SfxItemIter it{rParaAttribs.GetItems()}; !it.IsAtEnd(); it.NextItem())
+    {
+        YrsInsertAttribImplImpl(yw, *it.GetCurItem(), i, 1);
+    }
+}
+
+void YrsInsertText(IYrsTransactionSupplier *const pYrsSupplier, OString const& 
rCommentId,
+    EditDoc const& rDoc, uint32_t const index, uint32_t const content, 
::std::u16string_view const rText)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += content;
+    OString const text{::rtl::OUStringToOString(rText, RTL_TEXTENCODING_UTF8)};
+    ytext_insert(yw.pText, yw.pTxn, i, text.getStr(), nullptr);
+}
+
+void YrsConnectPara(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId,
+    EditDoc const& rDoc, uint32_t const index, uint32_t const pos)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += pos;
+    ytext_remove_range(yw.pText, yw.pTxn, i, 1);
+}
+
+void YrsRemoveChars(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId,
+    EditDoc const& rDoc, uint32_t const index, uint32_t const content, 
uint32_t const length)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += content;
+    ytext_remove_range(yw.pText, yw.pTxn, i, length);
+}
+
+void YrsSetStyle(IYrsTransactionSupplier *const pYrsSupplier, OString const& 
rCommentId,
+    EditDoc const& rDoc, uint32_t const index, ::std::u16string_view const 
rStyle)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += rDoc.GetObject(index)->Len();
+    OString const styleName{OUStringToOString(rStyle, RTL_TEXTENCODING_UTF8)};
+    YInput const style{yinput_string(styleName.getStr())};
+    YInput attrArray[]{ style };
+    char const*const attrNames[]{ "para-style" };
+    YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), 
attrArray, 1)};
+    ytext_format(yw.pText, yw.pTxn, i, 1, &attrs);
+}
+
+void YrsSetParaAttr(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId,
+    EditDoc const& rDoc, uint32_t const index, SfxPoolItem const& rItem)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    i += rDoc.GetObject(index)->Len();
+    YrsInsertAttribImplImpl(yw, rItem, i, 1);
+}
+
+char const* YrsWhichToAttrName(sal_Int16 const nWhich)
+{
+    switch (nWhich)
+    {
+        case EE_CHAR_COLOR:
+            return "EE_CHAR_COLOR";
+        case EE_CHAR_BKGCOLOR:
+            return "EE_CHAR_BKGCOLOR";
+        case EE_CHAR_FONTINFO:
+            return "EE_CHAR_FONTINFO";
+        case EE_CHAR_FONTINFO_CJK:
+            return "EE_CHAR_FONTINFO_CJK";
+        case EE_CHAR_FONTINFO_CTL:
+            return "EE_CHAR_FONTINFO_CTL";
+        case EE_CHAR_FONTHEIGHT:
+            return "EE_CHAR_FONTHEIGHT";
+        case EE_CHAR_FONTHEIGHT_CJK:
+            return "EE_CHAR_FONTHEIGHT_CJK";
+        case EE_CHAR_FONTHEIGHT_CTL:
+            return "EE_CHAR_FONTHEIGHT_CTL";
+        case EE_CHAR_FONTWIDTH:
+            return "EE_CHAR_FONTWIDTH";
+        case EE_CHAR_WEIGHT:
+            return "EE_CHAR_WEIGHT";
+        case EE_CHAR_WEIGHT_CJK:
+            return "EE_CHAR_WEIGHT_CJK";
+        case EE_CHAR_WEIGHT_CTL:
+            return "EE_CHAR_WEIGHT_CTL";
+        case EE_CHAR_UNDERLINE:
+            return "EE_CHAR_UNDERLINE";
+        case EE_CHAR_OVERLINE:
+            return "EE_CHAR_OVERLINE";
+        case EE_CHAR_STRIKEOUT:
+            return "EE_CHAR_STRIKEOUT";
+        case EE_CHAR_ITALIC:
+            return "EE_CHAR_ITALIC";
+        case EE_CHAR_ITALIC_CJK:
+            return "EE_CHAR_ITALIC_CJK";
+        case EE_CHAR_ITALIC_CTL:
+            return "EE_CHAR_ITALIC_CTL";
+        case EE_CHAR_OUTLINE:
+            return "EE_CHAR_OUTLINE";
+        case EE_CHAR_SHADOW:
+            return "EE_CHAR_SHADOW";
+        case EE_CHAR_ESCAPEMENT:
+            return "EE_CHAR_ESCAPEMENT";
+        case EE_CHAR_PAIRKERNING:
+            return "EE_CHAR_PAIRKERNING";
+        case EE_CHAR_KERNING:
+            return "EE_CHAR_KERNING";
+        case EE_CHAR_WLM:
+            return "EE_CHAR_WLM";
+        case EE_CHAR_LANGUAGE:
+            return "EE_CHAR_LANGUAGE";
+        case EE_CHAR_LANGUAGE_CJK:
+            return "EE_CHAR_LANGUAGE_CJK";
+        case EE_CHAR_LANGUAGE_CTL:
+            return "EE_CHAR_LANGUAGE_CTL";
+        case EE_CHAR_EMPHASISMARK:
+            return "EE_CHAR_EMPHASISMARK";
+        case EE_CHAR_RELIEF:
+            return "EE_CHAR_RELIEF";
+        case EE_CHAR_CASEMAP:
+            return "EE_CHAR_CASEMAP";
+        case EE_PARA_WRITINGDIR:
+            return "EE_PARA_WRITINGDIR";
+        case EE_PARA_HANGINGPUNCTUATION:
+            return "EE_PARA_HANGINGPUNCTUATION";
+        case EE_PARA_FORBIDDENRULES:
+            return "EE_PARA_FORBIDDENRULES";
+        case EE_PARA_ASIANCJKSPACING:
+            return "EE_PARA_ASIANCJKSPACING";
+//TODO complex, but apparently no way to set this in comment? inline constexpr 
TypedWhichId<SvxNumBulletItem>          EE_PARA_NUMBULLET          
(EE_PARA_START+5);
+        case EE_PARA_HYPHENATE:
+            return "EE_PARA_HYPHENATE";
+        case EE_PARA_HYPHENATE_NO_CAPS:
+            return "EE_PARA_HYPHENATE_NO_CAPS";
+        case EE_PARA_HYPHENATE_NO_LAST_WORD:
+            return "EE_PARA_HYPHENATE_NO_LAST_WORD";
+        case EE_PARA_BULLETSTATE:
+            return "EE_PARA_BULLETSTATE";
+//TODO no way to set this in comment? inline constexpr 
TypedWhichId<SvxLRSpaceItem>            EE_PARA_OUTLLRSPACE        
(EE_PARA_START+10);
+        case EE_PARA_OUTLLEVEL:
+            return "EE_PARA_OUTLLEVEL";
+//TODO complex, but apparently no way to set this in comment? inline constexpr 
TypedWhichId<SvxBulletItem>             EE_PARA_BULLET             
(EE_PARA_START+12);
+        case EE_PARA_LRSPACE:
+            return "EE_PARA_LRSPACE";
+        case EE_PARA_ULSPACE:
+            return "EE_PARA_ULSPACE";
+        case EE_PARA_SBL:
+            return "EE_PARA_SBL";
+        case EE_PARA_JUST:
+            return "EE_PARA_JUST";
+        case EE_PARA_TABS:
+            return "EE_PARA_TABS";
+        case EE_PARA_JUST_METHOD:
+            return "EE_PARA_JUST_METHOD";
+        case EE_PARA_VER_JUST:
+            return "EE_PARA_VER_JUST";
+        default:
+            assert(false);
+    }
+}
+
+void YrsRemoveAttrib(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId,
+    EditDoc const& rDoc, uint32_t const index, sal_uInt16 const nWhich, 
sal_Int32 const nStart, sal_Int32 const nEnd)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    YInput const attr{yinput_null()};
+    char const*const attrName{YrsWhichToAttrName(nWhich)};
+    YInput const attrs{yinput_json_map(const_cast<char**>(&attrName), 
const_cast<YInput*>(&attr), 1)};
+    ytext_format(yw.pText, yw.pTxn, i + nStart, nEnd - nStart, &attrs);
+}
+
+void YrsInsertAttrib(IYrsTransactionSupplier *const pYrsSupplier, OString 
const& rCommentId, EditDoc const& rDoc, uint32_t const index, EditCharAttrib 
const*const pAttr)
+{
+    YrsWrite const yw{GetYrsWrite(pYrsSupplier, rCommentId)};
+    if (yw.pTxn == nullptr)
+    {
+        return;
+    }
+    uint32_t i{0};
+    for (auto paras{index}; paras != 0; --paras)
+    {
+        i += rDoc.GetObject(paras-1)->Len() + 1;
+    }
+    YrsInsertAttribImpl(yw, i, pAttr);
+}
+
+uint64_t YrsReadInt(YOutput const& rValue)
+{
+    // with the v1 encoding, JSON is being sent apparently (like "family":2) , 
which has issues with integers being sometimes read as floats so workaround here
+    if (rValue.tag == Y_JSON_INT)
+    {
+        return rValue.value.integer;
+    }
+    else
+    {
+        yvalidate(rValue.tag == Y_JSON_NUM);
+        return ::std::lround(rValue.value.num);
+    }
+}
+
+void YrsImplInsertAttr(SfxItemSet & rSet, ::std::vector<sal_uInt16> *const 
pRemoved,
+    char const*const pKey, YOutput const& rValue)
+{
+    sal_uInt16 nWhich{0};
+    if (strcmp(pKey, "EE_CHAR_COLOR") == 0)
+    {
+        nWhich = EE_CHAR_COLOR;
+    }
+    else if (strcmp(pKey, "EE_CHAR_BKGCOLOR") == 0)
+    {
+        nWhich = EE_CHAR_BKGCOLOR;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTINFO") == 0)
+    {
+        nWhich = EE_CHAR_FONTINFO;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTINFO_CJK") == 0)
+    {
+        nWhich = EE_CHAR_FONTINFO_CJK;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTINFO_CTL") == 0)
+    {
+        nWhich = EE_CHAR_FONTINFO_CTL;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTHEIGHT") == 0)
+    {
+        nWhich = EE_CHAR_FONTHEIGHT;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTHEIGHT_CJK") == 0)
+    {
+        nWhich = EE_CHAR_FONTHEIGHT_CJK;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTHEIGHT_CTL") == 0)
+    {
+        nWhich = EE_CHAR_FONTHEIGHT_CTL;
+    }
+    else if (strcmp(pKey, "EE_CHAR_FONTWIDTH") == 0)
+    {
+        nWhich = EE_CHAR_FONTWIDTH;
+    }
+    else if (strcmp(pKey, "EE_CHAR_WEIGHT") == 0)
+    {
+        nWhich = EE_CHAR_WEIGHT;
+    }
+    else if (strcmp(pKey, "EE_CHAR_WEIGHT_CJK") == 0)
+    {
+        nWhich = EE_CHAR_WEIGHT_CJK;
+    }
+    else if (strcmp(pKey, "EE_CHAR_WEIGHT_CTL") == 0)
+    {
+        nWhich = EE_CHAR_WEIGHT_CTL;
+    }
+    else if (strcmp(pKey, "EE_CHAR_UNDERLINE") == 0)
+    {
+        nWhich = EE_CHAR_UNDERLINE;
+    }
+    else if (strcmp(pKey, "EE_CHAR_OVERLINE") == 0)
+    {
+        nWhich = EE_CHAR_OVERLINE;
+    }
+    else if (strcmp(pKey, "EE_CHAR_STRIKEOUT") == 0)
+    {
+        nWhich = EE_CHAR_STRIKEOUT;
+    }
+    else if (strcmp(pKey, "EE_CHAR_ITALIC") == 0)
+    {
+        nWhich = EE_CHAR_ITALIC;
+    }
+    else if (strcmp(pKey, "EE_CHAR_ITALIC_CJK") == 0)
+    {
+        nWhich = EE_CHAR_ITALIC_CJK;
+    }
+    else if (strcmp(pKey, "EE_CHAR_ITALIC_CTL") == 0)
+    {
+        nWhich = EE_CHAR_ITALIC_CTL;
+    }
+    else if (strcmp(pKey, "EE_CHAR_OUTLINE") == 0)
+    {
+        nWhich = EE_CHAR_OUTLINE;
+    }
+    else if (strcmp(pKey, "EE_CHAR_SHADOW") == 0)
+    {
+        nWhich = EE_CHAR_SHADOW;
+    }
+    else if (strcmp(pKey, "EE_CHAR_ESCAPEMENT") == 0)
+    {
+        nWhich = EE_CHAR_ESCAPEMENT;
+    }
+    else if (strcmp(pKey, "EE_CHAR_PAIRKERNING") == 0)
+    {
+        nWhich = EE_CHAR_PAIRKERNING;
+    }
+    else if (strcmp(pKey, "EE_CHAR_KERNING") == 0)
+    {
+        nWhich = EE_CHAR_KERNING;
+    }
+    else if (strcmp(pKey, "EE_CHAR_WLM") == 0)
+    {
+        nWhich = EE_CHAR_WLM;
+    }
+    else if (strcmp(pKey, "EE_CHAR_LANGUAGE") == 0)
+    {
+        nWhich = EE_CHAR_LANGUAGE;
+    }
+    else if (strcmp(pKey, "EE_CHAR_LANGUAGE_CJK") == 0)
+    {
+        nWhich = EE_CHAR_LANGUAGE_CJK;
+    }
+    else if (strcmp(pKey, "EE_CHAR_LANGUAGE_CTL") == 0)
+    {
+        nWhich = EE_CHAR_LANGUAGE_CTL;
+    }
+    else if (strcmp(pKey, "EE_CHAR_EMPHASISMARK") == 0)
+    {
+        nWhich = EE_CHAR_EMPHASISMARK;
+    }
+    else if (strcmp(pKey, "EE_CHAR_RELIEF") == 0)
+    {
+        nWhich = EE_CHAR_RELIEF;
+    }
+    else if (strcmp(pKey, "EE_CHAR_CASEMAP") == 0)
+    {
+        nWhich = EE_CHAR_CASEMAP;
+    }
+    else if (strcmp(pKey, "EE_PARA_WRITINGDIR") == 0)
+    {
+        nWhich = EE_PARA_WRITINGDIR;
+    }
+    else if (strcmp(pKey, "EE_PARA_HANGINGPUNCTUATION") == 0)
+    {
+        nWhich = EE_PARA_HANGINGPUNCTUATION;
+    }
+    else if (strcmp(pKey, "EE_PARA_FORBIDDENRULES") == 0)
+    {
+        nWhich = EE_PARA_FORBIDDENRULES;
+    }
+    else if (strcmp(pKey, "EE_PARA_ASIANCJKSPACING") == 0)
+    {
+        nWhich = EE_PARA_ASIANCJKSPACING;
+    }
+    else if (strcmp(pKey, "EE_PARA_HYPHENATE") == 0)
+    {
+        nWhich = EE_PARA_HYPHENATE;
+    }
+    else if (strcmp(pKey, "EE_PARA_HYPHENATE_NO_CAPS") == 0)
+    {
+        nWhich = EE_PARA_HYPHENATE_NO_CAPS;
+    }
+    else if (strcmp(pKey, "EE_PARA_HYPHENATE_NO_LAST_WORD") == 0)
+    {
+        nWhich = EE_PARA_HYPHENATE_NO_LAST_WORD;
+    }
+    else if (strcmp(pKey, "EE_PARA_BULLETSTATE") == 0)
+    {
+        nWhich = EE_PARA_BULLETSTATE;
+    }
+    else if (strcmp(pKey, "EE_PARA_OUTLLEVEL") == 0)
+    {
+        nWhich = EE_PARA_OUTLLEVEL;
+    }
+    else if (strcmp(pKey, "EE_PARA_LRSPACE") == 0)
+    {
+        nWhich = EE_PARA_LRSPACE;
+    }
+    else if (strcmp(pKey, "EE_PARA_ULSPACE") == 0)
+    {
+        nWhich = EE_PARA_ULSPACE;
+    }
+    else if (strcmp(pKey, "EE_PARA_SBL") == 0)
+    {
+        nWhich = EE_PARA_SBL;
+    }
+    else if (strcmp(pKey, "EE_PARA_JUST") == 0)
+    {
+        nWhich = EE_PARA_JUST;
+    }
+    else if (strcmp(pKey, "EE_PARA_TABS") == 0)
+    {
+        nWhich = EE_PARA_TABS;
+    }
+    else if (strcmp(pKey, "EE_PARA_JUST_METHOD") == 0)
+    {
+        nWhich = EE_PARA_JUST_METHOD;
+    }
+    else if (strcmp(pKey, "EE_PARA_VER_JUST") == 0)
+    {
+        nWhich = EE_PARA_VER_JUST;
+    }
+    else if (pKey[0] == 'E' && pKey[1] == 'E' && pKey[2] == '_')
+    {
+        abort();
+    }
+    else
+    {
+        return;
+    }
+
+    if (rValue.tag == Y_JSON_NULL)
+    {
+        assert(pRemoved);
+        if (pRemoved)
+        {
+            pRemoved->emplace_back(nWhich);
+        }
+        return;
+    }
+    else switch (nWhich)
+    {
+        case EE_CHAR_COLOR:
+        case EE_CHAR_BKGCOLOR:
+        {
+            Color const c(ColorTransparency, YrsReadInt(rValue));
+            SvxColorItem const item(c, nWhich);
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_FONTINFO:
+        case EE_CHAR_FONTINFO_CJK:
+        case EE_CHAR_FONTINFO_CTL:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<OUString> oFamilyName;
+            ::std::optional<OUString> oStyle;
+            ::std::optional<FontFamily> oFamily;
+            ::std::optional<FontPitch> oPitch;
+            ::std::optional<rtl_TextEncoding> oCharset;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "familyname") == 0)
+                {
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_STR);
+                    
oFamilyName.emplace(OStringToOUString(rValue.value.map[i].value->value.str, 
RTL_TEXTENCODING_UTF8));
+                }
+                else if (strcmp(pEntry, "style") == 0)
+                {
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_STR);
+                    
oStyle.emplace(OStringToOUString(rValue.value.map[i].value->value.str, 
RTL_TEXTENCODING_UTF8));
+                }
+                else if (strcmp(pEntry, "family") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(FAMILY_DONTKNOW <= value && value <= 
FAMILY_SYSTEM);
+                    oFamily.emplace(FontFamily(value));
+                }
+                else if (strcmp(pEntry, "pitch") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(PITCH_DONTKNOW <= value && value <= 
PITCH_VARIABLE);
+                    oPitch.emplace(FontPitch(value));
+                }
+                else if (strcmp(pEntry, "charset") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*RTL_TEXTENCODING_DONTKNOW <= value &&*/ value 
<= RTL_TEXTENCODING_UNICODE);
+                    oCharset.emplace(rtl_TextEncoding(value));
+                }
+                else yvalidate(false);
+            }
+            if (oFamilyName && oStyle && oFamily && oPitch && oCharset)
+            {
+                SvxFontItem const item{
+                    *oFamily, *oFamilyName, *oStyle, *oPitch, *oCharset, 
nWhich};
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_CHAR_FONTHEIGHT:
+        case EE_CHAR_FONTHEIGHT_CJK:
+        case EE_CHAR_FONTHEIGHT_CTL:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<sal_uInt32> oHeight;
+            ::std::optional<sal_uInt16> oProp;
+            ::std::optional<MapUnit> oMapUnit;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "height") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_INT32);
+                    oHeight.emplace(value);
+                }
+                else if (strcmp(pEntry, "prop") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_INT16);
+                    oProp.emplace(value);
+                }
+                else if (strcmp(pEntry, "propunit") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= 
::std::underlying_type_t<MapUnit>(MapUnit::LAST));
+                    oMapUnit.emplace(MapUnit(value));
+                }
+                else yvalidate(false);
+            }
+            if (oHeight && oProp && oMapUnit)
+            {
+                SvxFontHeightItem item{*oHeight, 100, nWhich};
+                item.SetProp(*oProp, *oMapUnit);
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_CHAR_FONTWIDTH:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate(/*0 <= value && */value <= SAL_MAX_INT16);
+            SvxCharScaleWidthItem const item{sal_uInt16(value), 
TypedWhichId<SvxCharScaleWidthItem>(nWhich)};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_WEIGHT:
+        case EE_CHAR_WEIGHT_CJK:
+        case EE_CHAR_WEIGHT_CTL:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate(WEIGHT_DONTKNOW <= value && value <= WEIGHT_BLACK);
+            SvxWeightItem const item{FontWeight(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_UNDERLINE:
+        case EE_CHAR_OVERLINE:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<FontLineStyle> oStyle;
+            ::std::optional<Color> oColor;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "style") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(LINESTYLE_NONE <= value && value <= 
LINESTYLE_BOLDWAVE);
+                    oStyle.emplace(FontLineStyle(value));
+                }
+                else if (strcmp(pEntry, "color") == 0)
+                {
+                    oColor.emplace(ColorTransparency, 
YrsReadInt(*rValue.value.map[i].value));
+                }
+                else yvalidate(false);
+            }
+            if (oStyle && oColor)
+            {
+                if (nWhich == EE_CHAR_UNDERLINE)
+                {
+                    SvxUnderlineItem item{*oStyle, EE_CHAR_UNDERLINE};
+                    item.SetColor(*oColor);
+                    rSet.Put(item);
+                }
+                else
+                {
+                    SvxOverlineItem item{*oStyle, EE_CHAR_OVERLINE};
+                    item.SetColor(*oColor);
+                    rSet.Put(item);
+                }
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_CHAR_STRIKEOUT:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate(STRIKEOUT_NONE <= value && value <= STRIKEOUT_X);
+            SvxCrossedOutItem const item{FontStrikeout(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_ITALIC:
+        case EE_CHAR_ITALIC_CJK:
+        case EE_CHAR_ITALIC_CTL:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate(ITALIC_NONE <= value && value <= ITALIC_DONTKNOW);
+            SvxPostureItem const item{FontItalic(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_OUTLINE:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxContourItem const item{rValue.value.flag == Y_TRUE, nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_SHADOW:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxShadowedItem const item{rValue.value.flag == Y_TRUE, nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_ESCAPEMENT:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<short> oEsc;
+            ::std::optional<sal_uInt8> oProp;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "esc") == 0)
+                {
+                    int64_t const 
value{static_cast<int64_t>(YrsReadInt(*rValue.value.map[i].value))};
+                    yvalidate(SAL_MIN_INT16 <= value && value <= 
SAL_MAX_INT16);
+                    oEsc.emplace(short(value));
+                }
+                else if (strcmp(pEntry, "prop") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT8);
+                    oProp.emplace(sal_uInt8(value));
+                }
+                else yvalidate(false);
+            }
+            if (oEsc && oProp)
+            {
+                SvxEscapementItem const item{*oEsc, *oProp, 
EE_CHAR_ESCAPEMENT};
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_CHAR_PAIRKERNING:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxAutoKernItem const item{rValue.value.flag == Y_TRUE, nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_KERNING:
+        {
+            int64_t const value{static_cast<int64_t>(YrsReadInt(rValue))};
+            yvalidate(SAL_MIN_INT16 <= value && value <= SAL_MAX_INT16);
+            SvxKerningItem const item{short(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_WLM:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxWordLineModeItem const item{rValue.value.flag == Y_TRUE, 
nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_LANGUAGE:
+        case EE_CHAR_LANGUAGE_CJK:
+        case EE_CHAR_LANGUAGE_CTL:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+            SvxLanguageItem const item{LanguageType(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_EMPHASISMARK:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate((value & 0x300f) == value);
+            SvxEmphasisMarkItem const item{FontEmphasisMark(value), 
TypedWhichId<SvxEmphasisMarkItem>(nWhich)};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_RELIEF:
+        {
+            auto const value{YrsReadInt(rValue)};
+            yvalidate(::std::underlying_type_t<FontRelief>(FontRelief::NONE) 
<= value && value <= 
::std::underlying_type_t<FontRelief>(FontRelief::Engraved));
+            SvxCharReliefItem const item{FontRelief(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_CHAR_CASEMAP:
+        {
+            auto const value{YrsReadInt(rValue)};
+            
yvalidate(::std::underlying_type_t<SvxCaseMap>(SvxCaseMap::NotMapped) <= value 
&& value < ::std::underlying_type_t<SvxCaseMap>(SvxCaseMap::End));
+            SvxCaseMapItem const item{SvxCaseMap(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_WRITINGDIR:
+        {
+            auto const value{YrsReadInt(rValue)};
+            
yvalidate(::std::underlying_type_t<SvxFrameDirection>(SvxFrameDirection::Horizontal_LR_TB)
 <= value && value < 
::std::underlying_type_t<SvxFrameDirection>(SvxFrameDirection::Stacked));
+            SvxFrameDirectionItem const item{SvxFrameDirection(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_HANGINGPUNCTUATION:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxHangingPunctuationItem const item{rValue.value.flag == Y_TRUE, 
nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_FORBIDDENRULES:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxForbiddenRuleItem const item{rValue.value.flag == Y_TRUE, 
nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_ASIANCJKSPACING:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SvxScriptSpaceItem const item{rValue.value.flag == Y_TRUE, nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_HYPHENATE:
+        case EE_PARA_HYPHENATE_NO_CAPS:
+        case EE_PARA_HYPHENATE_NO_LAST_WORD:
+        case EE_PARA_BULLETSTATE:
+        {
+            yvalidate(rValue.tag == Y_JSON_BOOL);
+            SfxBoolItem const item{nWhich, rValue.value.flag == Y_TRUE};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_OUTLLEVEL:
+        {
+            int64_t const value{static_cast<int64_t>(YrsReadInt(rValue))};
+            yvalidate(-1 <= value && value < SVX_MAX_NUM);
+            SfxInt16Item const item{nWhich, sal_Int16(value)};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_LRSPACE:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<double> oFirstLineIndent;
+            ::std::optional<sal_Int16> oFirstLineIndentUnit;
+            ::std::optional<double> oLeft;
+            ::std::optional<sal_Int16> oLeftUnit;
+            ::std::optional<double> oRight;
+            ::std::optional<sal_Int16> oRightUnit;
+            ::std::optional<bool> oAutoFirst;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "first-line-offset") == 0)
+                {
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_NUM);
+                    
oFirstLineIndent.emplace(rValue.value.map[i].value->value.num);
+                }
+                else if (strcmp(pEntry, "first-line-offset-unit") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(css::util::MeasureUnit::MM_100TH <= value && 
value <= css::util::MeasureUnit::FONT_CJK_ADVANCE);
+                    oFirstLineIndentUnit.emplace(sal_Int16(value));
+                }
+                else if (strcmp(pEntry, "left-margin") == 0)
+                {
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_NUM);
+                    oLeft.emplace(rValue.value.map[i].value->value.num);
+                }
+                else if (strcmp(pEntry, "left-margin-unit") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(css::util::MeasureUnit::MM_100TH <= value && 
value <= css::util::MeasureUnit::FONT_CJK_ADVANCE);
+                    oLeftUnit.emplace(sal_Int16(value));
+                }
+                else if (strcmp(pEntry, "right-margin") == 0)
+                {
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_NUM);
+                    oRight.emplace(rValue.value.map[i].value->value.num);
+                }
+                else if (strcmp(pEntry, "right-margin-unit") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(css::util::MeasureUnit::MM_100TH <= value && 
value <= css::util::MeasureUnit::FONT_CJK_ADVANCE);
+                    oRightUnit.emplace(sal_Int16(value));
+                }
+                else if (strcmp(pEntry, "auto-first") == 0)
+                {
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_BOOL);
+                    oAutoFirst.emplace(rValue.value.map[i].value->value.flag 
== Y_TRUE);
+                }
+                else yvalidate(false);
+            }
+            if (oFirstLineIndent && oFirstLineIndentUnit && 
oAutoFirst.has_value()
+                && oLeft && oLeftUnit && oRight && oRightUnit)
+            {
+                SvxLRSpaceItem item{{*oLeft, *oLeftUnit}, {*oRight, 
*oRightUnit}, {*oFirstLineIndent, *oFirstLineIndentUnit}, EE_PARA_LRSPACE};
+                item.SetAutoFirst(*oAutoFirst);
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_PARA_ULSPACE:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<sal_uInt16> oUpper;
+            ::std::optional<sal_uInt16> oLower;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "upper-margin") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+                    oUpper.emplace(sal_uInt16(value));
+                }
+                else if (strcmp(pEntry, "lower-margin") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+                    oLower.emplace(sal_uInt16(value));
+                }
+                else yvalidate(false);
+            }
+            if (oUpper && oLower)
+            {
+                SvxULSpaceItem const item{*oUpper, *oLower, EE_PARA_ULSPACE};
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_PARA_SBL:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<sal_uInt16> oLineSpaceFix;
+            ::std::optional<sal_uInt16> oLineSpaceMin;
+            ::std::optional<sal_uInt16> oInterLineSpaceProp;
+            ::std::optional<sal_Int16> oInterLineSpaceFix;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "line-space-fix") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+                    oLineSpaceFix.emplace(sal_uInt16(value));
+                }
+                else if (strcmp(pEntry, "line-space-min") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+                    oLineSpaceMin.emplace(sal_uInt16(value));
+                }
+                else if (strcmp(pEntry, "inter-line-space-prop") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+                    oInterLineSpaceProp.emplace(sal_uInt16(value));
+                }
+                else if (strcmp(pEntry, "inter-line-space-fix") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_UINT16);
+                    oInterLineSpaceFix.emplace(sal_Int16(value));
+                }
+                else yvalidate(false);
+            }
+            SvxLineSpacingItem item{0, EE_PARA_SBL};
+            if (oLineSpaceFix)
+            {
+                item.SetLineHeight(*oLineSpaceFix);
+                item.SetLineSpaceRule(SvxLineSpaceRule::Fix);
+                rSet.Put(item);
+            }
+            else if (oLineSpaceMin)
+            {
+                item.SetLineHeight(*oLineSpaceMin);
+                rSet.Put(item);
+            }
+            else if (oInterLineSpaceProp)
+            {
+                item.SetPropLineSpace(*oInterLineSpaceProp);
+                rSet.Put(item);
+            }
+            else if (oInterLineSpaceFix)
+            {
+                item.SetInterLineSpace(*oInterLineSpaceFix);
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_PARA_JUST:
+        {
+            auto const value{YrsReadInt(rValue)};
+            SvxAdjustItem item{SvxAdjust(), nWhich};
+            switch (value)
+            {
+                case uint64_t(SvxAdjust::Left):
+                case uint64_t(SvxAdjust::Right):
+                case uint64_t(SvxAdjust::Center):
+                    item.SetAdjust(SvxAdjust(value));
+                    break;
+                case uint64_t(SvxAdjust::Block):
+                    item.SetAdjust(SvxAdjust(value));
+                    item.SetLastBlock(SvxAdjust::Left);
+                    break;
+                case 5:
+                    item.SetAdjust(SvxAdjust::Block);
+                    item.SetLastBlock(SvxAdjust::Center);
+                    break;
+                case 6:
+                    item.SetAdjust(SvxAdjust::Block);
+                    item.SetLastBlock(SvxAdjust::Block);
+                    item.SetOneWord(SvxAdjust::Left);
+                    break;
+                case 7:
+                    item.SetAdjust(SvxAdjust::Block);
+                    item.SetLastBlock(SvxAdjust::Block);
+                    item.SetOneWord(SvxAdjust::Block);
+                    break;
+                default:
+                    abort();
+            }
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_TABS:
+        {
+            yvalidate(rValue.tag == Y_JSON_MAP);
+            ::std::optional<sal_Int32> oDefault;
+            ::std::optional<::std::vector<SvxTabStop>> oTabs;
+            for (decltype(rValue.len) i = 0; i < rValue.len; ++i)
+            {
+                const char*const pEntry{rValue.value.map[i].key};
+                if (strcmp(pEntry, "default-distance") == 0)
+                {
+                    auto const value{YrsReadInt(*rValue.value.map[i].value)};
+                    yvalidate(/*0 <= value && */value <= SAL_MAX_INT32);
+                    oDefault.emplace(sal_Int32(value));
+                }
+                else if (strcmp(pEntry, "tab-stops") == 0)
+                {
+                    oTabs.emplace();
+                    yvalidate(rValue.value.map[i].value->tag == Y_JSON_ARR);
+                    YOutput const& rArray{*rValue.value.map[i].value};
+                    for (decltype(rArray.len) j = 0; j < rArray.len; ++j)
+                    {
+                        YOutput const& rMap{rArray.value.array[j]};
+                        yvalidate(rMap.tag == Y_JSON_MAP);
+                        ::std::optional<sal_Int32> oPos;
+                        ::std::optional<SvxTabAdjust> oAdjust;
+                        ::std::optional<sal_Unicode> oDecimal;
+                        ::std::optional<sal_Unicode> oFill;
+                        for (decltype(rMap.len) k = 0; k < rMap.len; ++k)
+                        {
+                            const char*const pE{rMap.value.map[k].key};
+                            if (strcmp(pE, "pos") == 0)
+                            {
+                                auto const 
value{YrsReadInt(*rMap.value.map[k].value)};
+                                yvalidate(/*0 <= value && */value <= 
SAL_MAX_INT32);
+                                oPos.emplace(sal_Int32(value));
+                            }
+                            else if (strcmp(pE, "adjustment") == 0)
+                            {
+                                auto const 
value{YrsReadInt(*rMap.value.map[k].value)};
+                                
yvalidate(::std::underlying_type_t<SvxTabAdjust>(SvxTabAdjust::Left) <= value 
&& value < ::std::underlying_type_t<SvxTabAdjust>(SvxTabAdjust::End));
+                                oAdjust.emplace(SvxTabAdjust(value));
+                            }
+                            else if (strcmp(pE, "decimal") == 0)
+                            {
+                                auto const 
value{YrsReadInt(*rMap.value.map[k].value)};
+                                yvalidate(/*0 <= value && */value < 
SAL_MAX_UINT16);
+                                oDecimal.emplace(sal_Unicode(value));
+                            }
+                            else if (strcmp(pE, "fill") == 0)
+                            {
+                                auto const 
value{YrsReadInt(*rMap.value.map[k].value)};
+                                yvalidate(/*0 <= value && */value < 
SAL_MAX_UINT16);
+                                oFill.emplace(sal_Unicode(value));
+                            }
+                            else yvalidate(false);
+                        }
+                        if (oPos && oAdjust && oDecimal && oFill)
+                        {
+                            oTabs->emplace_back(*oPos, *oAdjust, *oDecimal, 
*oFill);
+                        }
+                        else yvalidate(false);
+                    }
+                }
+            }
+            if (oDefault && oTabs)
+            {
+                SvxTabStopItem item{nWhich};
+                item.SetDefaultDistance(*oDefault);
+                for (SvxTabStop const& it : *oTabs)
+                {
+                    item.Insert(it);
+                }
+                rSet.Put(item);
+            }
+            else yvalidate(false);
+            break;
+        }
+        case EE_PARA_JUST_METHOD:
+        {
+            auto const value{YrsReadInt(rValue)};
+            
yvalidate(::std::underlying_type_t<SvxCellJustifyMethod>(SvxCellJustifyMethod::Auto)
 <= value && value <= 
::std::underlying_type_t<SvxCellJustifyMethod>(SvxCellJustifyMethod::Distribute));
+            SvxJustifyMethodItem const item{SvxCellJustifyMethod(value), 
nWhich};
+            rSet.Put(item);
+            break;
+        }
+        case EE_PARA_VER_JUST:
+        {
+            auto const value{YrsReadInt(rValue)};
+            
yvalidate(::std::underlying_type_t<SvxCellVerJustify>(SvxCellVerJustify::Standard)
 <= value && value <= 
::std::underlying_type_t<SvxCellVerJustify>(SvxCellVerJustify::Block));
+            SvxVerJustifyItem const item{SvxCellVerJustify(value), nWhich};
+            rSet.Put(item);
+            break;
+        }
+
+        default:
+            assert(false);
+    }
+}
+
+// TODO: this could be a lot simpler if feature were a nested json-map like 
attr
+template<typename T> void
+YrsImplInsertFeature(ImpEditEngine & rIEE,
+    EditPaM const& rPam,
+    T const*const elements, uint32_t const len,
+    ::std::function<::std::pair<char const*, YOutput const&>(T const&)> 
pGetAttr)
+{
+    ::std::optional<OUString> oFeature;
+    ::std::optional<SvxURLFormat> oFormat;
+    ::std::optional<OUString> oURL;
+    ::std::optional<OUString> oRepresentation;
+    ::std::optional<OUString> oTargetFrame;
+    for (::std::remove_const_t<decltype(len)> k = 0; k < len; ++k)
+    {
+        T const& rAttr{elements[k]};
+        auto [pKey, rValue]{pGetAttr(rAttr)};
+        if (strcmp(pKey, "feature") == 0)
+        {
+            yvalidate(rValue.tag == Y_JSON_STR);
+            oFeature.emplace(OStringToOUString(rValue.value.str, 
RTL_TEXTENCODING_UTF8));
+        }
+        else if (strcmp(pKey, "url-format") == 0)
+        {
+            uint64_t const value{YrsReadInt(rValue)};
+            switch (value)
+            {
+                case 
::std::underlying_type_t<SvxURLFormat>(SvxURLFormat::AppDefault):
+                case ::std::underlying_type_t<SvxURLFormat>(SvxURLFormat::Url):
+                case 
::std::underlying_type_t<SvxURLFormat>(SvxURLFormat::Repr):
+                    oFormat.emplace(SvxURLFormat(value));
+                    break;
+                default:
+                    yvalidate(false);
+            }
+        }
+        else if (strcmp(pKey, "url-url") == 0)
+        {
+            yvalidate(rValue.tag == Y_JSON_STR);
+            oURL.emplace(OStringToOUString(rValue.value.str, 
RTL_TEXTENCODING_UTF8));
+        }
+        else if (strcmp(pKey, "url-representation") == 0)
+        {
+            yvalidate(rValue.tag == Y_JSON_STR);
+            oRepresentation.emplace(OStringToOUString(rValue.value.str, 
RTL_TEXTENCODING_UTF8));
+        }
+        else if (strcmp(pKey, "url-targetframe") == 0)
+        {
+            yvalidate(rValue.tag == Y_JSON_STR);
+            oTargetFrame.emplace(OStringToOUString(rValue.value.str, 
RTL_TEXTENCODING_UTF8));
+        }
+    }
+    if (oFeature && *oFeature == "tab")
+    {
+        EditSelection const sel{rPam};
+        rIEE.InsertTab(sel);
+    }
+    else if (oFeature && *oFeature == "line")
+    {
+        EditSelection const sel{rPam};
+        rIEE.InsertLineBreak(sel);
+    }
+    else if (oFeature && *oFeature == "url")
+    {
+        if (oFormat && oURL && oRepresentation)
+        {
+            SvxURLField field{*oURL, *oRepresentation, *oFormat};
+            if (oTargetFrame)
+            {
+                field.SetTargetFrame(*oTargetFrame);
+            }
+            SvxFieldItem const item{field, EE_FEATURE_FIELD};
+            EditSelection const sel{rPam};
+            rIEE.InsertField(sel, item);
+        }
+        else yvalidate(false);
+    }
+    else
+    {
+        yvalidate(false);
+    }
+}
+
+} // namespace
+
+void EditDoc::YrsSetStyle(sal_Int32 const index, ::std::u16string_view const 
rStyle)
+{
+    ::YrsSetStyle(m_pYrsSupplier, m_CommentId, *this, index, rStyle);
+}
+
+void EditDoc::YrsSetParaAttr(sal_Int32 const index, SfxPoolItem const& rItem)
+{
+    ::YrsSetParaAttr(m_pYrsSupplier, m_CommentId, *this, index, rItem);
+}
+
+void EditDoc::YrsWriteEEState()
+{
+    assert(m_pYrsSupplier);
+
+    YrsWrite const yw{GetYrsWrite(m_pYrsSupplier, m_CommentId)};
+    assert(yw.pTxn && yw.pProps && yw.pText);
+
+    YInput const vertical{yinput_bool(mbIsVertical ? Y_TRUE : Y_FALSE)};
+    ymap_insert(yw.pProps, yw.pTxn, "is-vertical", &vertical);
+    YInput const rotation{yinput_long(static_cast<int64_t>(mnRotation))};
+    ymap_insert(yw.pProps, yw.pTxn, "rotation", &rotation);
+    YInput const deftab{yinput_long(static_cast<int64_t>(mnDefTab))};
+    ymap_insert(yw.pProps, yw.pTxn, "def-tab", &deftab);
+
+    for (::std::unique_ptr<ContentNode> const& rpNode : maContents)
+    {
+        auto const start{ytext_len(yw.pText, yw.pTxn)};
+        sal_Int32 pos{0};
+        for (sal_Int32 i = rpNode->GetString().indexOf(CH_FEATURE, pos);
+              i != -1; i = rpNode->GetString().indexOf(CH_FEATURE, pos))
+        {
+            if (i != pos)
+            {
+                OString const 
content{OUStringToOString(rpNode->GetString().subView(pos, i - pos), 
RTL_TEXTENCODING_UTF8)};
+                ytext_insert(yw.pText, yw.pTxn, start + pos, content.getStr(), 
nullptr);
+            }
+            EditCharAttrib const*const 
pAttrib{rpNode->GetCharAttribs().FindFeature(i)};
+            assert(pAttrib);
+            // this will insert the CH_FEATURE too
+            YrsInsertFeature(m_pYrsSupplier, m_CommentId, *this, 
GetPos(rpNode.get()), pAttrib);
+            pos = i+1;
+        }
+        if (pos != rpNode->GetString().getLength())
+        {
+            OString const 
content{OUStringToOString(rpNode->GetString().subView(pos, 
rpNode->GetString().getLength() - pos), RTL_TEXTENCODING_UTF8)};
+            ytext_insert(yw.pText, yw.pTxn, start + pos, content.getStr(), 
nullptr);
+        }
+        for (::std::unique_ptr<EditCharAttrib> const& rpAttr : 
rpNode->GetCharAttribs().GetAttribs())
+        {
+            if (!rpAttr->IsFeature())
+            {
+                YrsInsertAttribImpl(yw, start, rpAttr.get());
+            }
+        }
+        char const para[]{ CH_PARA, '
+        ContentAttribs const& rParaAttribs{rpNode->GetContentAttribs()};
+        OString const 
styleName{OUStringToOString(rpNode->GetStyleSheet()->GetName(), 
RTL_TEXTENCODING_UTF8)};
+        YInput const style{yinput_string(styleName.getStr())};
+        YInput attrArray[]{ style };
+        char const*const attrNames[]{ "para-style" };
+        YInput const attrs{yinput_json_map(const_cast<char**>(attrNames), 
attrArray, 1)};
+        auto const end{ytext_len(yw.pText, yw.pTxn)};
+        ytext_insert(yw.pText, yw.pTxn, end, para, &attrs);
+        for (SfxItemIter it{rParaAttribs.GetItems()}; !it.IsAtEnd(); 
it.NextItem())
+        {
+            YrsInsertAttribImplImpl(yw, *it.GetCurItem(), end, 1);
+        }
+    }
+}
+
+void EditDoc::YrsReadEEState(YTransaction *const pTxn, ImpEditEngine & rIEE)
+{
+    assert(m_pYrsSupplier);
+    YrsWrite const yw{GetYrsWrite(m_pYrsSupplier, m_CommentId, pTxn)};
+    assert(yw.pTxn && yw.pProps && yw.pText);
+
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pVertical{ymap_get(yw.pProps, yw.pTxn, "is-vertical")};
+    yvalidate(!pVertical || pVertical->tag == Y_JSON_BOOL);
+    if (pVertical && pVertical->tag == Y_JSON_BOOL)
+    {
+        SetVertical(pVertical->value.flag == Y_TRUE);
+    }
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pRotation{ymap_get(yw.pProps, yw.pTxn, "rotation")};
+    yvalidate(pRotation);
+    {
+        auto const value{YrsReadInt(*pRotation)};
+        switch (value)
+        {
+            case int64_t(TextRotation::NONE):
+            case int64_t(TextRotation::TOPTOBOTTOM):
+            case int64_t(TextRotation::BOTTOMTOTOP):
+                SetRotation(TextRotation(value));
+                break;
+            default:
+                yvalidate(false);
+        }
+    }
+    ::std::unique_ptr<YOutput, YOutputDeleter> const 
pDefTab{ymap_get(yw.pProps, yw.pTxn, "def-tab")};
+    yvalidate(pDefTab);
+    {
+        auto const value{YrsReadInt(*pDefTab)};
+        yvalidate(/*0 <= value && */value <= SAL_MAX_INT16);
+        SetDefTab(value);
+    }
+
+    assert(maContents.size() == 1);
+#if 0
+    // there is a getImpl().GetEditSelection() too, who knows what else... try 
to reuse existing node?
+    rIEE.GetParaPortions().Reset();
+    maContents.clear();
+    // the last paragraph cannot be removed, as cursors and a11y need it
+    rIEE.RemoveParagraph(0); // remove pre-existing one from InitDoc()
+#endif
+    uint32_t chunks{0};
+    YChunk *const pChunks{ytext_chunks(yw.pText, yw.pTxn, &chunks)};
+    sal_Int32 nodes{0};
+    ContentNode * pNode{nullptr};
+    for (decltype(chunks) i = 0; i < chunks; ++i)
+    {
+        decltype(nodes) const nodeStart{nodes};
+        sal_Int32 const posStart{pNode ? pNode->Len() : 0};
+
+        yvalidate(pChunks[i].data.tag == Y_JSON_STR);
+        OString const str(pChunks[i].data.value.str);
+        sal_Int32 strStart{0};
+
+        for (sal_Int32 j = 0; j < str.getLength(); ++j)
+        {
+            if (!pNode)
+            {
+                //pNode = new ContentNode(GetItemPool());
+                //rIEE.InsertContent(pNode, nodes); // does not set DefFont?
+                pNode = rIEE.ImpFastInsertParagraph(nodes).GetNode();
+//                rIEE.GetParaPortions().Insert(nodes, 
::std::make_unique<ParaPortion>(pNode));
+//does not call ParagraphInserted so no a11y event                
Insert(nodes, ::std::unique_ptr<ContentNode>(pNode));
+                // calling UpdateSelections() is pointless, it only handles 
deletes
+            }
+            if (str[j] == CH_PARA)
+            {
+                if (j != 0)
+                {
+                    OString const portion{str.copy(strStart, j - strStart)};
+                    pNode->Append(OStringToOUString(portion, 
RTL_TEXTENCODING_UTF8));
+                }
+                for (decltype(pChunks[i].fmt_len) k = 0; k < 
pChunks[i].fmt_len; ++k)
+                {
+                    if (strcmp(pChunks[i].fmt[k].key, "para-style") == 0)
+                    {
+                        yvalidate(pChunks[i].fmt[k].value->tag == Y_JSON_STR);
+                        OUString const 
style{OStringToOUString(pChunks[i].fmt[k].value->value.str, 
RTL_TEXTENCODING_UTF8)};
+                        SfxStyleSheet *const pStyle{dynamic_cast<SfxStyleSheet 
*>(
+                            rIEE.GetStyleSheetPool()->Find(style, 
SfxStyleFamily::Para))};
+                        if (!pStyle) { abort(); }
+                        pNode->SetStyleSheet(pStyle);
+                    }
+                }
+                pNode = nullptr;
+                ++nodes;
+                strStart = j+1;
+            }
+            else if (str[j] == CH_FEATURE)
+            {
+                if (j != strStart)
+                {
+                    OString const portion{str.copy(strStart, j - strStart)};
+                    pNode->Append(OStringToOUString(portion, 
RTL_TEXTENCODING_UTF8));
+                }
+                auto GetAttr = [](YMapEntry const& rEntry) -> ::std::pair<char 
const*, YOutput const&>
+                    { return {rEntry.key, *rEntry.value}; };
+                EditPaM const pam{pNode, pNode->Len()};
+                YrsImplInsertFeature<YMapEntry>(rIEE, pam, pChunks[i].fmt, 
pChunks[i].fmt_len, GetAttr);
+                strStart = j+1;
+            }
+        }
+        if (strStart != str.getLength())
+        {
+            OString const portion{str.copy(strStart, str.getLength() - 
strStart)};
+            pNode->Append(OStringToOUString(portion, RTL_TEXTENCODING_UTF8));
+        }
+        assert((pNode == nullptr) == (str[str.getLength()-1] == CH_PARA));
+        ContentNode *const pEndNode{pNode ? pNode : maContents[nodes-1].get()};
+        EditSelection const sel{EditPaM(maContents[nodeStart].get(), 
posStart), EditPaM(pEndNode, pEndNode->Len())};
+        SfxItemSet set{rIEE.GetEmptyItemSet()};
+        for (decltype(pChunks[i].fmt_len) j = 0; j < pChunks[i].fmt_len; ++j)
+        {
+            YrsImplInsertAttr(set, nullptr, pChunks[i].fmt[j].key, 
*pChunks[i].fmt[j].value);
+        }
+        if (set.Count())
+        {
+            rIEE.SetAttribs(sel, set);
+        }
+    }
+    ychunks_destroy(pChunks, chunks);
+    rIEE.RemoveParagraph(nodes); // remove pre-existing one from InitDoc()
+}
+
+static void YrsAdjustCursors(ImpEditEngine & rIEE, EditDoc & rDoc,
+    sal_Int32 const node, sal_Int32 const pos, ContentNode *const pNewNode, 
sal_Int32 const delta)
+{
+    for (EditView *const pView : rIEE.GetEditViews())
+    {
+        bool bSet{false};
+        EditSelection sel{pView->getImpl().GetEditSelection()};
+        ContentNode const*const pNode{rDoc.GetObject(node)};
+        if (sel.Min().GetNode() == pNode
+            && pos <= sel.Min().GetIndex())
+        {
+            sel.Min().SetNode(pNewNode);
+            sel.Min().SetIndex(sel.Min().GetIndex() + delta);
+            bSet = true;
+        }
+        if (sel.Max().GetNode() == pNode
+            && pos <= sel.Max().GetIndex())
+        {
+            sel.Max().SetNode(pNewNode);
+            sel.Max().SetIndex(sel.Max().GetIndex() + delta);
+            bSet = true;
+        }
+        if (bSet)
+        {
+            pView->getImpl().SetEditSelection(sel);
+        }
+    }
+}
+
+// TODO test this
+static void YrsAdjustCursorsDel(ImpEditEngine & rIEE, EditDoc & rDoc,
-e 
... etc. - the rest is truncated

Reply via email to