gnucash stable: Multiple changes pushed

Christopher Lam clam at code.gnucash.org
Sat Dec 13 02:00:04 EST 2025


Updated	 via  https://github.com/Gnucash/gnucash/commit/455ea3e7 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e79db945 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/753e4354 (commit)
	from  https://github.com/Gnucash/gnucash/commit/48e1d765 (commit)



commit 455ea3e71a46d8d8baa33d4bae0ec38a5525c171
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Mon Dec 8 12:35:13 2025 +0800

    [backend/xml] dom_tree_to_text returns std::optional<std::string>

diff --git a/libgnucash/backend/xml/gnc-freqspec-xml-v2.cpp b/libgnucash/backend/xml/gnc-freqspec-xml-v2.cpp
index b266adedce..2bb083ace2 100644
--- a/libgnucash/backend/xml/gnc-freqspec-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-freqspec-xml-v2.cpp
@@ -135,21 +135,18 @@ fs_uift_handler (xmlNodePtr node, gpointer data)
 {
     fsParseData* fspd = static_cast<decltype (fspd)> (data);
     int            i;
-    char*        nodeTxt;
 
-    nodeTxt = dom_tree_to_text (node);
+    auto nodeTxt = dom_tree_to_text (node);
 
     g_return_val_if_fail (nodeTxt, FALSE);
     for (i = 0; uiFreqTypeStrs[i].str != NULL; i++)
     {
-        if (g_strcmp0 (nodeTxt, uiFreqTypeStrs[i].str) == 0)
+        if (g_strcmp0 (nodeTxt->c_str(), uiFreqTypeStrs[i].str) == 0)
         {
             fspd->uift = uiFreqTypeStrs[i].uift;
-            g_free (nodeTxt);
             return TRUE;
         }
     }
-    g_free (nodeTxt);
     return FALSE;
 }
 
diff --git a/libgnucash/backend/xml/io-example-account.cpp b/libgnucash/backend/xml/io-example-account.cpp
index 1d89137186..3614c89479 100644
--- a/libgnucash/backend/xml/io-example-account.cpp
+++ b/libgnucash/backend/xml/io-example-account.cpp
@@ -210,7 +210,9 @@ squash_extra_whitespace (char* text)
 static char*
 grab_clean_string (xmlNodePtr tree)
 {
-    return squash_extra_whitespace (g_strstrip (dom_tree_to_text (tree)));
+    auto txt = dom_tree_to_text (tree);
+    auto str = g_strdup (txt ? txt->c_str() : "");
+    return squash_extra_whitespace (g_strstrip (str));
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/io-gncxml-v2.cpp b/libgnucash/backend/xml/io-gncxml-v2.cpp
index 327dae7226..3265ae2929 100644
--- a/libgnucash/backend/xml/io-gncxml-v2.cpp
+++ b/libgnucash/backend/xml/io-gncxml-v2.cpp
@@ -395,7 +395,7 @@ gnc_counter_end_handler (gpointer data_for_children,
     {
         auto strval = dom_tree_to_text (tree);
         PERR ("string_to_gint64 failed with input: %s",
-              strval ? strval : "(null)");
+              strval ? strval->c_str() : "(null)");
         ret = FALSE;
     }
     else if (g_strcmp0 (type, "transaction") == 0)
diff --git a/libgnucash/backend/xml/sixtp-dom-parsers.cpp b/libgnucash/backend/xml/sixtp-dom-parsers.cpp
index 6b9dcaaa6b..4f62bebdb2 100644
--- a/libgnucash/backend/xml/sixtp-dom-parsers.cpp
+++ b/libgnucash/backend/xml/sixtp-dom-parsers.cpp
@@ -320,19 +320,19 @@ dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame)
         if (g_strcmp0 ((char*)mark->name, "slot") == 0)
         {
             xmlNodePtr mark2;
-            gchar* key = NULL;
+            const gchar* key = NULL;
+            std::optional<std::string> maybe_key;
             KvpValue* val = NULL;
-            bool must_free{false};
 
             for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next)
             {
                 if (g_strcmp0 ((char*)mark2->name, "slot:key") == 0)
                 {
-                    key = const_cast<char*>(dom_node_to_text (mark2));
+                    key = dom_node_to_text (mark2);
                     if (!key)
                     {
-                        key = dom_tree_to_text (mark2);
-                        must_free = true;
+                        maybe_key = dom_tree_to_text (mark2);
+                        key = maybe_key ? maybe_key->c_str() : nullptr;
                     }
                 }
                 else if (g_strcmp0 ((char*)mark2->name, "slot:value") == 0)
@@ -357,8 +357,6 @@ dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame)
                 {
                     /* FIXME: should put some error here */
                 }
-                if (must_free)
-                    g_free (key);
             }
         }
     }
@@ -388,7 +386,7 @@ dom_tree_create_instance_slots (xmlNodePtr node, QofInstance* inst)
     return dom_tree_to_kvp_frame_given (node, frame);
 }
 
-gchar*
+std::optional<std::string>
 dom_tree_to_text (xmlNodePtr tree)
 {
     /* Expect *only* text and comment sibling nodes in the given tree --
@@ -400,29 +398,29 @@ dom_tree_to_text (xmlNodePtr tree)
        Ignores comment nodes and collapse text nodes into one string.
        Returns NULL if expectations are unsatisfied.
     */
-    gchar* result;
+    std::string rv;
     gchar* temp;
 
-    g_return_val_if_fail (tree, NULL);
+    g_return_val_if_fail (tree, std::nullopt);
 
     /* no nodes means it's an empty string text */
     if (!tree->xmlChildrenNode)
     {
         DEBUG ("No children");
-        return g_strdup ("");
+        return "";
     }
 
     temp = (char*)xmlNodeListGetString (NULL, tree->xmlChildrenNode, TRUE);
     if (!temp)
     {
         DEBUG ("Null string");
-        return NULL;
+        return std::nullopt;
     }
 
     DEBUG ("node string [%s]", (temp == NULL ? "(null)" : temp));
-    result = g_strdup (temp);
+    rv = temp;
     xmlFree (temp);
-    return result;
+    return rv;
 }
 
 gnc_numeric
diff --git a/libgnucash/backend/xml/sixtp-dom-parsers.h b/libgnucash/backend/xml/sixtp-dom-parsers.h
index 0538b4b68a..5f554520a0 100644
--- a/libgnucash/backend/xml/sixtp-dom-parsers.h
+++ b/libgnucash/backend/xml/sixtp-dom-parsers.h
@@ -46,7 +46,7 @@ time64 dom_tree_to_time64 (xmlNodePtr node);
 gboolean dom_tree_valid_time64 (time64 ts, const xmlChar* name);
 GDate* dom_tree_to_gdate (xmlNodePtr node);
 gnc_numeric dom_tree_to_gnc_numeric (xmlNodePtr node);
-gchar* dom_tree_to_text (xmlNodePtr tree);
+std::optional<std::string> dom_tree_to_text (xmlNodePtr tree);
 const char* dom_node_to_text (xmlNodePtr node) noexcept;
 gboolean string_to_binary (const gchar* str,  void** v, guint64* data_len);
 gboolean dom_tree_create_instance_slots (xmlNodePtr node, QofInstance* inst);
@@ -85,11 +85,7 @@ apply_xmlnode_text (F&& f, xmlNodePtr node, T default_val = T{})
         return f(txt);
 
     if (auto txt = dom_tree_to_text(node))
-    {
-        auto rv = f(txt);
-        g_free(txt);
-        return rv;
-    }
+        return f(txt->c_str());
 
     return default_val;
 }
diff --git a/libgnucash/backend/xml/test/test-dom-converters1.cpp b/libgnucash/backend/xml/test/test-dom-converters1.cpp
index 62fdfaa33b..55be6622ca 100644
--- a/libgnucash/backend/xml/test/test-dom-converters1.cpp
+++ b/libgnucash/backend/xml/test/test-dom-converters1.cpp
@@ -90,7 +90,6 @@ test_dom_tree_to_text (void)
     for (i = 0; i < 20; i++)
     {
         gchar* test_string1;
-        gchar* test_string2;
         xmlNodePtr test_node;
 
         test_node = xmlNewNode (NULL, BAD_CAST "test-node");
@@ -98,7 +97,7 @@ test_dom_tree_to_text (void)
 
         xmlNodeAddContent (test_node, BAD_CAST test_string1);
 
-        test_string2 = dom_tree_to_text (test_node);
+        auto test_string2 = dom_tree_to_text (test_node);
 
         if (!test_string2)
         {
@@ -106,7 +105,7 @@ test_dom_tree_to_text (void)
                           "null return from dom_tree_to_text");
             xmlElemDump (stdout, NULL, test_node);
         }
-        else if (g_strcmp0 (test_string1, test_string2) == 0)
+        else if (g_strcmp0 (test_string1, test_string2->c_str()) == 0)
         {
             success_args ("dom_tree_to_text", __FILE__, __LINE__, "with string %s",
                           test_string1);
@@ -119,7 +118,6 @@ test_dom_tree_to_text (void)
 
         xmlFreeNode (test_node);
         g_free (test_string1);
-        if (test_string2) g_free (test_string2);
     }
 }
 
diff --git a/libgnucash/backend/xml/test/test-file-stuff.cpp b/libgnucash/backend/xml/test/test-file-stuff.cpp
index 21ffb93f10..5d50a8f7b9 100644
--- a/libgnucash/backend/xml/test/test-file-stuff.cpp
+++ b/libgnucash/backend/xml/test/test-file-stuff.cpp
@@ -142,26 +142,22 @@ check_dom_tree_version (xmlNodePtr node,  const char* verstr)
 gboolean
 equals_node_val_vs_string (xmlNodePtr node, const gchar* str)
 {
-    gchar* cmp1;
-
     g_return_val_if_fail (node, FALSE);
     g_return_val_if_fail (str, FALSE);
 
-    cmp1 = dom_tree_to_text (node);
+    auto cmp1 = dom_tree_to_text (node);
 
     if (!cmp1)
     {
         return FALSE;
     }
-    else if (g_strcmp0 (cmp1, str) == 0)
+    else if (g_strcmp0 (cmp1->c_str(), str) == 0)
     {
-        g_free (cmp1);
         return TRUE;
     }
     else
     {
-        printf ("Differing types: node:`%s' vs string:`%s'\n", cmp1, str);
-        g_free (cmp1);
+        printf ("Differing types: node:`%s' vs string:`%s'\n", cmp1->c_str(), str);
         return FALSE;
     }
 }
@@ -169,21 +165,17 @@ equals_node_val_vs_string (xmlNodePtr node, const gchar* str)
 gboolean
 equals_node_val_vs_int (xmlNodePtr node, gint64 val)
 {
-    gchar* text;
     gint64 test_val;
 
     g_return_val_if_fail (node, FALSE);
 
-    text = dom_tree_to_text (node);
+    auto text = dom_tree_to_text (node);
 
-    if (!string_to_gint64 (text, &test_val))
+    if (!text || !string_to_gint64 (*text, &test_val))
     {
-        g_free (text);
         return FALSE;
     }
 
-    g_free (text);
-
     return val == test_val;
 }
 
diff --git a/libgnucash/backend/xml/test/test-string-converters.cpp b/libgnucash/backend/xml/test/test-string-converters.cpp
index 4dcd104d8d..3f045ae4d2 100644
--- a/libgnucash/backend/xml/test/test-string-converters.cpp
+++ b/libgnucash/backend/xml/test/test-string-converters.cpp
@@ -54,13 +54,12 @@ test_string_converters (void)
     {
         const char* mark = test_strings[i];
         xmlNodePtr test_node = text_to_dom_tree ("test-string", mark);
-        char* backout = dom_tree_to_text (test_node);
+        auto backout = dom_tree_to_text (test_node);
 
         do_test_args (
-            g_strcmp0 (backout, mark) == 0,
+            g_strcmp0 (backout->c_str(), mark) == 0,
             "string converting", __FILE__, __LINE__, "with string %s", mark);
 
-        g_free (backout);
         xmlFreeNode (test_node);
     }
 }
@@ -72,12 +71,11 @@ test_bad_string (void)
     const char* sanitized = "foo?bar";
     xmlNodePtr test_node = text_to_dom_tree ("test-string", badstr);
 
-    char* backout = dom_tree_to_text (test_node);
-    do_test_args (g_strcmp0 (backout, sanitized) == 0,
+    auto backout = dom_tree_to_text (test_node);
+    do_test_args (g_strcmp0 (backout->c_str(), sanitized) == 0,
                   "string sanitizing", __FILE__, __LINE__,
                   "with string %s", badstr);
 
-    g_free (backout);
     xmlFreeNode (test_node);
 }
 
diff --git a/libgnucash/backend/xml/test/test-xml-account.cpp b/libgnucash/backend/xml/test/test-xml-account.cpp
index bc5d541fe5..5c6917ec71 100644
--- a/libgnucash/backend/xml/test/test-xml-account.cpp
+++ b/libgnucash/backend/xml/test/test-xml-account.cpp
@@ -88,29 +88,22 @@ node_and_account_equal (xmlNodePtr node, Account* act)
         }
         else if (g_strcmp0 ((char*)mark->name, "act:type") == 0)
         {
-            gchar* txt;
             GNCAccountType type;
 
-            txt = dom_tree_to_text (mark);
+            auto txt = dom_tree_to_text (mark);
 
             if (!txt)
             {
                 return g_strdup ("couldn't get type string");
             }
-            else if (!xaccAccountStringToType (txt, &type))
+            else if (!xaccAccountStringToType (txt->c_str(), &type))
             {
-                g_free (txt);
                 return g_strdup ("couldn't convert type string to int");
             }
             else if (type != xaccAccountGetType (act))
             {
-                g_free (txt);
                 return g_strdup ("types differ");
             }
-            else
-            {
-                g_free (txt);
-            }
         }
         else if (g_strcmp0 ((char*)mark->name, "act:commodity") == 0)
         {
diff --git a/libgnucash/backend/xml/test/test-xml-commodity.cpp b/libgnucash/backend/xml/test/test-xml-commodity.cpp
index 58683a75ea..ccbe55ecdd 100644
--- a/libgnucash/backend/xml/test/test-xml-commodity.cpp
+++ b/libgnucash/backend/xml/test/test-xml-commodity.cpp
@@ -105,30 +105,23 @@ node_and_commodity_equal (xmlNodePtr node, const gnc_commodity* com)
         }
         else if (g_strcmp0 ((char*)mark->name, "cmdty:fraction") == 0)
         {
-            gchar* txt;
             gint64 type;
 
-            txt = dom_tree_to_text (mark);
+            auto txt = dom_tree_to_text (mark);
 
             if (!txt)
             {
                 return "couldn't get fraction string";
             }
 
-            else if (!string_to_gint64 (txt, &type))
+            else if (!string_to_gint64 (*txt, &type))
             {
-                g_free (txt);
                 return "couldn't convert fraction string to int";
             }
             else if (type != gnc_commodity_get_fraction (com))
             {
-                g_free (txt);
                 return "fractions differ";
             }
-            else
-            {
-                g_free (txt);
-            }
         }
         else if (g_strcmp0 ((char*)mark->name, "cmdty:slots") == 0)
         {
diff --git a/libgnucash/backend/xml/test/test-xml-transaction.cpp b/libgnucash/backend/xml/test/test-xml-transaction.cpp
index 74815f9835..c9d6a991c5 100644
--- a/libgnucash/backend/xml/test/test-xml-transaction.cpp
+++ b/libgnucash/backend/xml/test/test-xml-transaction.cpp
@@ -116,25 +116,21 @@ equals_node_val_vs_split_internal (xmlNodePtr node, Split* spl)
         }
         else if (g_strcmp0 ((char*)mark->name, "split:memo") == 0)
         {
-            char* memo = dom_tree_to_text (mark);
+            auto memo = dom_tree_to_text (mark);
 
-            if (g_strcmp0 (memo, xaccSplitGetMemo (spl)) != 0)
+            if (g_strcmp0 (memo->c_str(), xaccSplitGetMemo (spl)) != 0)
             {
-                g_free (memo);
                 return "memos differ";
             }
-            g_free (memo);
         }
         else if (g_strcmp0 ((char*)mark->name, "split:reconciled-state") == 0)
         {
-            char* rs = dom_tree_to_text (mark);
+            auto rs = dom_tree_to_text (mark);
 
-            if (rs[0] != xaccSplitGetReconcile (spl))
+            if (!rs || rs->front() != xaccSplitGetReconcile (spl))
             {
-                g_free (rs);
                 return "states differ";
             }
-            g_free (rs);
         }
         else if (g_strcmp0 ((char*)mark->name, "split:value") == 0)
         {

commit e79db945b2ea6fd2e107cafd7ba502fd82db4cd7
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Dec 9 17:16:42 2025 +0800

    [backend/xml] use apply_xmlnode_text

diff --git a/libgnucash/backend/xml/gnc-account-xml-v2.cpp b/libgnucash/backend/xml/gnc-account-xml-v2.cpp
index 620150a068..11e04dbec9 100644
--- a/libgnucash/backend/xml/gnc-account-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-account-xml-v2.cpp
@@ -171,32 +171,12 @@ struct account_pdata
     QofBook* book;
 };
 
-static inline gboolean
-set_string (xmlNodePtr node, Account* act,
-            void (*func) (Account* act, const gchar* txt))
-{
-    if (auto txt = dom_node_to_text (node))
-    {
-        func (act,txt);
-        return TRUE;
-    }
-
-    gchar* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (act, txt);
-
-    g_free (txt);
-
-    return TRUE;
-}
-
 static gboolean
 account_name_handler (xmlNodePtr node, gpointer act_pdata)
 {
     struct account_pdata* pdata = static_cast<decltype (pdata)> (act_pdata);
 
-    return set_string (node, pdata->account, xaccAccountSetName);
+    return apply_xmlnode_text (xaccAccountSetName, pdata->account, node);
 }
 
 static gboolean
@@ -390,7 +370,7 @@ account_code_handler (xmlNodePtr node, gpointer act_pdata)
 {
     struct account_pdata* pdata = static_cast<decltype (pdata)> (act_pdata);
 
-    return set_string (node, pdata->account, xaccAccountSetCode);
+    return apply_xmlnode_text (xaccAccountSetCode, pdata->account, node);
 }
 
 static gboolean
@@ -398,7 +378,7 @@ account_description_handler (xmlNodePtr node, gpointer act_pdata)
 {
     struct account_pdata* pdata = static_cast<decltype (pdata)> (act_pdata);
 
-    return set_string (node, pdata->account, xaccAccountSetDescription);
+    return apply_xmlnode_text (xaccAccountSetDescription, pdata->account, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-address-xml-v2.cpp b/libgnucash/backend/xml/gnc-address-xml-v2.cpp
index bf53fa3c20..9cbaa673af 100644
--- a/libgnucash/backend/xml/gnc-address-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-address-xml-v2.cpp
@@ -96,26 +96,13 @@ struct address_pdata
     GncAddress* address;
 };
 
-static gboolean
-set_string (xmlNodePtr node, GncAddress* addr,
-            void (*func) (GncAddress* addr, const char* txt))
-{
-    gchar* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (addr, txt);
-
-    g_free (txt);
-
-    return TRUE;
-}
 
 static gboolean
 address_name_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetName);
+    return apply_xmlnode_text (gncAddressSetName, pdata->address, node);
 }
 
 static gboolean
@@ -123,7 +110,7 @@ address_addr1_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetAddr1);
+    return apply_xmlnode_text (gncAddressSetAddr1, pdata->address, node);
 }
 
 static gboolean
@@ -131,7 +118,7 @@ address_addr2_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetAddr2);
+    return apply_xmlnode_text (gncAddressSetAddr2, pdata->address, node);
 }
 
 static gboolean
@@ -139,7 +126,7 @@ address_addr3_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetAddr3);
+    return apply_xmlnode_text (gncAddressSetAddr3, pdata->address, node);
 }
 
 static gboolean
@@ -147,7 +134,7 @@ address_addr4_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetAddr4);
+    return apply_xmlnode_text (gncAddressSetAddr4, pdata->address, node);
 }
 
 static gboolean
@@ -155,7 +142,7 @@ address_phone_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetPhone);
+    return apply_xmlnode_text (gncAddressSetPhone, pdata->address, node);
 }
 
 static gboolean
@@ -163,7 +150,7 @@ address_fax_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetFax);
+    return apply_xmlnode_text (gncAddressSetFax, pdata->address, node);
 }
 
 static gboolean
@@ -171,7 +158,7 @@ address_email_handler (xmlNodePtr node, gpointer addr_pdata)
 {
     struct address_pdata* pdata = static_cast<decltype (pdata)> (addr_pdata);
 
-    return set_string (node, pdata->address, gncAddressSetEmail);
+    return apply_xmlnode_text (gncAddressSetEmail, pdata->address, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-bill-term-xml-v2.cpp b/libgnucash/backend/xml/gnc-bill-term-xml-v2.cpp
index 9de99ed97c..bfa7bf7676 100644
--- a/libgnucash/backend/xml/gnc-bill-term-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-bill-term-xml-v2.cpp
@@ -275,17 +275,6 @@ set_parent_child (xmlNodePtr node, struct billterm_pdata* pdata,
     return TRUE;
 }
 
-static gboolean
-set_string (xmlNodePtr node, GncBillTerm* term,
-            void (*func) (GncBillTerm*, const char*))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-    func (term, txt);
-    g_free (txt);
-    return TRUE;
-}
-
 static gboolean
 billterm_guid_handler (xmlNodePtr node, gpointer billterm_pdata)
 {
@@ -313,14 +302,14 @@ static gboolean
 billterm_name_handler (xmlNodePtr node, gpointer billterm_pdata)
 {
     struct billterm_pdata* pdata = static_cast<decltype (pdata)> (billterm_pdata);
-    return set_string (node, pdata->term, gncBillTermSetName);
+    return apply_xmlnode_text (gncBillTermSetName, pdata->term, node);
 }
 
 static gboolean
 billterm_desc_handler (xmlNodePtr node, gpointer billterm_pdata)
 {
     struct billterm_pdata* pdata = static_cast<decltype (pdata)> (billterm_pdata);
-    return set_string (node, pdata->term, gncBillTermSetDescription);
+    return apply_xmlnode_text (gncBillTermSetDescription, pdata->term, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-budget-xml-v2.cpp b/libgnucash/backend/xml/gnc-budget-xml-v2.cpp
index 0215bf5ac9..2c1e970c18 100644
--- a/libgnucash/backend/xml/gnc-budget-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-budget-xml-v2.cpp
@@ -85,17 +85,6 @@ gnc_budget_dom_tree_create (GncBudget* bgt)
 }
 
 /***********************************************************************/
-static inline gboolean
-set_string (xmlNodePtr node, GncBudget* bgt,
-            void (*func) (GncBudget* bgt, const gchar* txt))
-{
-    gchar* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (bgt, txt);
-    g_free (txt);
-    return TRUE;
-}
 
 static gboolean
 budget_id_handler (xmlNodePtr node, gpointer bgt)
@@ -109,13 +98,13 @@ budget_id_handler (xmlNodePtr node, gpointer bgt)
 static gboolean
 budget_name_handler (xmlNodePtr node, gpointer bgt)
 {
-    return set_string (node, GNC_BUDGET (bgt), gnc_budget_set_name);
+    return apply_xmlnode_text (gnc_budget_set_name, GNC_BUDGET (bgt), node);
 }
 
 static gboolean
 budget_description_handler (xmlNodePtr node, gpointer bgt)
 {
-    return set_string (node, GNC_BUDGET (bgt), gnc_budget_set_description);
+    return apply_xmlnode_text (gnc_budget_set_description, GNC_BUDGET (bgt), node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-commodity-xml-v2.cpp b/libgnucash/backend/xml/gnc-commodity-xml-v2.cpp
index de5c5ca896..5335e2c677 100644
--- a/libgnucash/backend/xml/gnc-commodity-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-commodity-xml-v2.cpp
@@ -171,15 +171,18 @@ set_commodity_value (xmlNodePtr node, gnc_commodity* com)
     {
         struct com_char_handler* mark;
 
+        auto call_commodity_handler = [&](gnc_commodity* com, const char* txt)
+        {
+            auto val = gnc_strstrip (txt);
+            (mark->func) (com, val.c_str());
+        };
+
         for (mark = com_handlers; mark->tag; mark++)
         {
             if (g_strcmp0 (mark->tag, (char*)node->name) == 0)
             {
-                gchar* val = dom_tree_to_text (node);
-                g_strstrip (val);
-                (mark->func) (com, val);
-                g_free (val);
-                break;
+                if (apply_xmlnode_text (call_commodity_handler, com, node))
+                    break;
             }
         }
     }
diff --git a/libgnucash/backend/xml/gnc-customer-xml-v2.cpp b/libgnucash/backend/xml/gnc-customer-xml-v2.cpp
index 35c72e8532..9023269733 100644
--- a/libgnucash/backend/xml/gnc-customer-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-customer-xml-v2.cpp
@@ -145,19 +145,6 @@ struct customer_pdata
     QofBook* book;
 };
 
-static gboolean
-set_string (xmlNodePtr node, GncCustomer* cust,
-            void (*func) (GncCustomer* cust, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (cust, txt);
-
-    g_free (txt);
-
-    return TRUE;
-}
 
 static gboolean
 set_boolean (xmlNodePtr node, GncCustomer* cust,
@@ -178,7 +165,7 @@ customer_name_handler (xmlNodePtr node, gpointer cust_pdata)
 {
     struct customer_pdata* pdata = static_cast<decltype (pdata)> (cust_pdata);
 
-    return set_string (node, pdata->customer, gncCustomerSetName);
+    return apply_xmlnode_text (gncCustomerSetName, pdata->customer, node);
 }
 
 static gboolean
@@ -209,7 +196,7 @@ customer_id_handler (xmlNodePtr node, gpointer cust_pdata)
 {
     struct customer_pdata* pdata = static_cast<decltype (pdata)> (cust_pdata);
 
-    return set_string (node, pdata->customer, gncCustomerSetID);
+    return apply_xmlnode_text (gncCustomerSetID, pdata->customer, node);
 }
 
 static gboolean
@@ -217,7 +204,7 @@ customer_notes_handler (xmlNodePtr node, gpointer cust_pdata)
 {
     struct customer_pdata* pdata = static_cast<decltype (pdata)> (cust_pdata);
 
-    return set_string (node, pdata->customer, gncCustomerSetNotes);
+    return apply_xmlnode_text (gncCustomerSetNotes, pdata->customer, node);
 }
 
 static gboolean
@@ -257,20 +244,13 @@ static gboolean
 customer_taxincluded_handler (xmlNodePtr node, gpointer cust_pdata)
 {
     struct customer_pdata* pdata = static_cast<decltype (pdata)> (cust_pdata);
-    GncTaxIncluded type;
-    char* str;
-    gboolean ret;
-
-    str = dom_tree_to_text (node);
-    g_return_val_if_fail (str, FALSE);
-
-    ret = gncTaxIncludedStringToType (str, &type);
-    g_free (str);
-
-    if (ret)
-        gncCustomerSetTaxIncluded (pdata->customer, type);
-
-    return ret;
+    auto set_tax_included = [](GncCustomer* cust, const char *str)
+    {
+        GncTaxIncluded type;
+        if (gncTaxIncludedStringToType (str, &type))
+            gncCustomerSetTaxIncluded (cust, type);
+    };
+    return apply_xmlnode_text (set_tax_included, pdata->customer, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-employee-xml-v2.cpp b/libgnucash/backend/xml/gnc-employee-xml-v2.cpp
index cc8bb85b16..0115ac259f 100644
--- a/libgnucash/backend/xml/gnc-employee-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-employee-xml-v2.cpp
@@ -129,26 +129,12 @@ struct employee_pdata
     QofBook* book;
 };
 
-static gboolean
-set_string (xmlNodePtr node, GncEmployee* employee,
-            void (*func) (GncEmployee* employee, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (employee, txt);
-
-    g_free (txt);
-
-    return TRUE;
-}
-
 static gboolean
 employee_username_handler (xmlNodePtr node, gpointer employee_pdata)
 {
     struct employee_pdata* pdata = static_cast<decltype (pdata)> (employee_pdata);
 
-    return set_string (node, pdata->employee, gncEmployeeSetUsername);
+    return apply_xmlnode_text (gncEmployeeSetUsername, pdata->employee, node);
 }
 
 static gboolean
@@ -181,7 +167,7 @@ employee_id_handler (xmlNodePtr node, gpointer employee_pdata)
 {
     struct employee_pdata* pdata = static_cast<decltype (pdata)> (employee_pdata);
 
-    return set_string (node, pdata->employee, gncEmployeeSetID);
+    return apply_xmlnode_text (gncEmployeeSetID, pdata->employee, node);
 }
 
 static gboolean
@@ -189,7 +175,7 @@ employee_language_handler (xmlNodePtr node, gpointer employee_pdata)
 {
     struct employee_pdata* pdata = static_cast<decltype (pdata)> (employee_pdata);
 
-    return set_string (node, pdata->employee, gncEmployeeSetLanguage);
+    return apply_xmlnode_text (gncEmployeeSetLanguage, pdata->employee, node);
 }
 
 static gboolean
@@ -197,7 +183,7 @@ employee_acl_handler (xmlNodePtr node, gpointer employee_pdata)
 {
     struct employee_pdata* pdata = static_cast<decltype (pdata)> (employee_pdata);
 
-    return set_string (node, pdata->employee, gncEmployeeSetAcl);
+    return apply_xmlnode_text (gncEmployeeSetAcl, pdata->employee, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-entry-xml-v2.cpp b/libgnucash/backend/xml/gnc-entry-xml-v2.cpp
index 38c2c01a5b..fa61e29b97 100644
--- a/libgnucash/backend/xml/gnc-entry-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-entry-xml-v2.cpp
@@ -224,18 +224,6 @@ struct entry_pdata
     Account* acc;
 };
 
-static inline gboolean
-set_string (xmlNodePtr node, GncEntry* entry,
-            void (*func) (GncEntry* entry, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (entry, txt);
-    g_free (txt);
-    return TRUE;
-}
-
 static inline gboolean
 set_time64 (xmlNodePtr node, GncEntry* entry,
               void (*func) (GncEntry* entry, time64 ts))
@@ -349,7 +337,7 @@ entry_description_handler (xmlNodePtr node, gpointer entry_pdata)
 {
     struct entry_pdata* pdata = static_cast<decltype (pdata)> (entry_pdata);
 
-    return set_string (node, pdata->entry, gncEntrySetDescription);
+    return apply_xmlnode_text (gncEntrySetDescription, pdata->entry, node);
 }
 
 static gboolean
@@ -357,7 +345,7 @@ entry_action_handler (xmlNodePtr node, gpointer entry_pdata)
 {
     struct entry_pdata* pdata = static_cast<decltype (pdata)> (entry_pdata);
 
-    return set_string (node, pdata->entry, gncEntrySetAction);
+    return apply_xmlnode_text (gncEntrySetAction, pdata->entry, node);
 }
 
 static gboolean
@@ -365,7 +353,7 @@ entry_notes_handler (xmlNodePtr node, gpointer entry_pdata)
 {
     struct entry_pdata* pdata = static_cast<decltype (pdata)> (entry_pdata);
 
-    return set_string (node, pdata->entry, gncEntrySetNotes);
+    return apply_xmlnode_text (gncEntrySetNotes, pdata->entry, node);
 }
 
 static gboolean
@@ -405,40 +393,30 @@ static gboolean
 entry_idisctype_handler (xmlNodePtr node, gpointer entry_pdata)
 {
     struct entry_pdata* pdata = static_cast<decltype (pdata)> (entry_pdata);
-    GncAmountType type;
-    char* str;
-    gboolean ret;
-
-    str = dom_tree_to_text (node);
-    g_return_val_if_fail (str, FALSE);
-
-    ret = gncAmountStringToType (str, &type);
-    g_free (str);
-
-    if (ret)
-        gncEntrySetInvDiscountType (pdata->entry, type);
-
-    return ret;
+    auto entry = pdata->entry;
+    auto set_discount_type = [entry](auto str)
+    {
+        GncAmountType type;
+        if (!gncAmountStringToType (str, &type)) return false;
+        gncEntrySetInvDiscountType (entry, type);
+        return true;
+    };
+    return apply_xmlnode_text (set_discount_type, node, FALSE);
 }
 
 static gboolean
 entry_idischow_handler (xmlNodePtr node, gpointer entry_pdata)
 {
     struct entry_pdata* pdata = static_cast<decltype (pdata)> (entry_pdata);
-    GncDiscountHow how;
-    char* str;
-    gboolean ret;
-
-    str = dom_tree_to_text (node);
-    g_return_val_if_fail (str, FALSE);
-
-    ret = gncEntryDiscountStringToHow (str, &how);
-    g_free (str);
-
-    if (ret)
-        gncEntrySetInvDiscountHow (pdata->entry, how);
-
-    return ret;
+    auto entry = pdata->entry;
+    auto set_discount_how = [entry](auto str)
+    {
+        GncDiscountHow how;
+        if (!gncEntryDiscountStringToHow (str, &how)) return false;
+        gncEntrySetInvDiscountHow (entry, how);
+        return true;
+    };
+    return apply_xmlnode_text (set_discount_how, node, FALSE);
 }
 
 static gboolean
@@ -526,20 +504,15 @@ static gboolean
 entry_billpayment_handler (xmlNodePtr node, gpointer entry_pdata)
 {
     struct entry_pdata* pdata = static_cast<decltype (pdata)> (entry_pdata);
-    GncEntryPaymentType type;
-    char* str;
-    gboolean ret;
-
-    str = dom_tree_to_text (node);
-    g_return_val_if_fail (str, FALSE);
-
-    ret = gncEntryPaymentStringToType (str, &type);
-    g_free (str);
-
-    if (ret)
-        gncEntrySetBillPayment (pdata->entry, type);
-
-    return ret;
+    auto entry = pdata->entry;
+    auto set_billpayment = [entry](auto str)
+    {
+        GncEntryPaymentType type;
+        if (!gncEntryPaymentStringToType (str, &type)) return false;
+        gncEntrySetBillPayment (entry, type);
+        return true;
+    };
+    return apply_xmlnode_text (set_billpayment, node, FALSE);
 }
 
 /* The rest of the stuff */
diff --git a/libgnucash/backend/xml/gnc-invoice-xml-v2.cpp b/libgnucash/backend/xml/gnc-invoice-xml-v2.cpp
index b1f3bb5a82..9121a2bc72 100644
--- a/libgnucash/backend/xml/gnc-invoice-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-invoice-xml-v2.cpp
@@ -167,18 +167,6 @@ struct invoice_pdata
     QofBook* book;
 };
 
-static inline gboolean
-set_string (xmlNodePtr node, GncInvoice* invoice,
-            void (*func) (GncInvoice* invoice, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (invoice, txt);
-
-    g_free (txt);
-    return TRUE;
-}
 
 static inline gboolean
 set_time64 (xmlNodePtr node, GncInvoice* invoice,
@@ -218,7 +206,7 @@ invoice_id_handler (xmlNodePtr node, gpointer invoice_pdata)
 {
     struct invoice_pdata* pdata = static_cast<decltype (pdata)> (invoice_pdata);
 
-    return set_string (node, pdata->invoice, gncInvoiceSetID);
+    return apply_xmlnode_text (gncInvoiceSetID, pdata->invoice, node);
 }
 
 static gboolean
@@ -254,7 +242,7 @@ invoice_billing_id_handler (xmlNodePtr node, gpointer invoice_pdata)
 {
     struct invoice_pdata* pdata = static_cast<decltype (pdata)> (invoice_pdata);
 
-    return set_string (node, pdata->invoice, gncInvoiceSetBillingID);
+    return apply_xmlnode_text (gncInvoiceSetBillingID, pdata->invoice, node);
 }
 
 static gboolean
@@ -262,7 +250,7 @@ invoice_notes_handler (xmlNodePtr node, gpointer invoice_pdata)
 {
     struct invoice_pdata* pdata = static_cast<decltype (pdata)> (invoice_pdata);
 
-    return set_string (node, pdata->invoice, gncInvoiceSetNotes);
+    return apply_xmlnode_text (gncInvoiceSetNotes, pdata->invoice, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-job-xml-v2.cpp b/libgnucash/backend/xml/gnc-job-xml-v2.cpp
index 95aaeaa07f..040fea9c25 100644
--- a/libgnucash/backend/xml/gnc-job-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-job-xml-v2.cpp
@@ -100,26 +100,12 @@ struct job_pdata
     QofBook* book;
 };
 
-static gboolean
-set_string (xmlNodePtr node, GncJob* job,
-            void (*func) (GncJob* job, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (job, txt);
-
-    g_free (txt);
-
-    return TRUE;
-}
-
 static gboolean
 job_name_handler (xmlNodePtr node, gpointer job_pdata)
 {
     struct job_pdata* pdata = static_cast<decltype (pdata)> (job_pdata);
 
-    return set_string (node, pdata->job, gncJobSetName);
+    return apply_xmlnode_text (gncJobSetName, pdata->job, node);
 }
 
 static gboolean
@@ -150,7 +136,7 @@ job_id_handler (xmlNodePtr node, gpointer job_pdata)
 {
     struct job_pdata* pdata = static_cast<decltype (pdata)> (job_pdata);
 
-    return set_string (node, pdata->job, gncJobSetID);
+    return apply_xmlnode_text (gncJobSetID, pdata->job, node);
 }
 
 static gboolean
@@ -158,7 +144,7 @@ job_reference_handler (xmlNodePtr node, gpointer job_pdata)
 {
     struct job_pdata* pdata = static_cast<decltype (pdata)> (job_pdata);
 
-    return set_string (node, pdata->job, gncJobSetReference);
+    return apply_xmlnode_text (gncJobSetReference, pdata->job, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-order-xml-v2.cpp b/libgnucash/backend/xml/gnc-order-xml-v2.cpp
index f4d2a37ff3..96e03c8116 100644
--- a/libgnucash/backend/xml/gnc-order-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-order-xml-v2.cpp
@@ -114,19 +114,6 @@ struct order_pdata
     QofBook* book;
 };
 
-static inline gboolean
-set_string (xmlNodePtr node, GncOrder* order,
-            void (*func) (GncOrder* order, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (order, txt);
-
-    g_free (txt);
-    return TRUE;
-}
-
 static inline gboolean
 set_time64 (xmlNodePtr node, GncOrder* order,
               void (*func) (GncOrder* order, time64 tt))
@@ -165,7 +152,7 @@ order_id_handler (xmlNodePtr node, gpointer order_pdata)
 {
     struct order_pdata* pdata = static_cast<decltype (pdata)> (order_pdata);
 
-    return set_string (node, pdata->order, gncOrderSetID);
+    return apply_xmlnode_text (gncOrderSetID, pdata->order, node);
 }
 
 static gboolean
@@ -203,7 +190,7 @@ order_notes_handler (xmlNodePtr node, gpointer order_pdata)
 {
     struct order_pdata* pdata = static_cast<decltype (pdata)> (order_pdata);
 
-    return set_string (node, pdata->order, gncOrderSetNotes);
+    return apply_xmlnode_text (gncOrderSetNotes, pdata->order, node);
 }
 
 static gboolean
@@ -211,7 +198,7 @@ order_reference_handler (xmlNodePtr node, gpointer order_pdata)
 {
     struct order_pdata* pdata = static_cast<decltype (pdata)> (order_pdata);
 
-    return set_string (node, pdata->order, gncOrderSetReference);
+    return apply_xmlnode_text (gncOrderSetReference, pdata->order, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-owner-xml-v2.cpp b/libgnucash/backend/xml/gnc-owner-xml-v2.cpp
index ab8d5338bf..eed8b69b67 100644
--- a/libgnucash/backend/xml/gnc-owner-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-owner-xml-v2.cpp
@@ -100,26 +100,19 @@ static gboolean
 owner_type_handler (xmlNodePtr node, gpointer owner_pdata)
 {
     struct owner_pdata* pdata = static_cast<decltype (pdata)> (owner_pdata);
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    if (!g_strcmp0 (txt, GNC_ID_CUSTOMER))
-        gncOwnerInitCustomer (pdata->owner, NULL);
-    else if (!g_strcmp0 (txt, GNC_ID_JOB))
-        gncOwnerInitJob (pdata->owner, NULL);
-    else if (!g_strcmp0 (txt, GNC_ID_VENDOR))
-        gncOwnerInitVendor (pdata->owner, NULL);
-    else if (!g_strcmp0 (txt, GNC_ID_EMPLOYEE))
-        gncOwnerInitEmployee (pdata->owner, NULL);
-    else
+    GncOwner* owner = pdata->owner;
+    auto init_owner_type = [](GncOwner* owner, const char* txt)
     {
-        PWARN ("Unknown owner type: %s", txt);
-        g_free (txt);
-        return FALSE;
-    }
-
-    g_free (txt);
-    return TRUE;
+        if (!g_strcmp0 (txt, GNC_ID_CUSTOMER))
+            gncOwnerInitCustomer (owner, NULL);
+        else if (!g_strcmp0 (txt, GNC_ID_JOB))
+            gncOwnerInitJob (owner, NULL);
+        else if (!g_strcmp0 (txt, GNC_ID_VENDOR))
+            gncOwnerInitVendor (owner, NULL);
+        else if (!g_strcmp0 (txt, GNC_ID_EMPLOYEE))
+            gncOwnerInitEmployee (owner, NULL);
+    };
+    return apply_xmlnode_text (init_owner_type, owner, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-pricedb-xml-v2.cpp b/libgnucash/backend/xml/gnc-pricedb-xml-v2.cpp
index 12fd48e067..63d81052d2 100644
--- a/libgnucash/backend/xml/gnc-pricedb-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-pricedb-xml-v2.cpp
@@ -115,17 +115,13 @@ price_parse_xml_sub_node (GNCPrice* p, xmlNodePtr sub_node, QofBook* book)
     }
     else if (g_strcmp0 ("price:source", (char*)sub_node->name) == 0)
     {
-        char* text = dom_tree_to_text (sub_node);
-        if (!text) return FALSE;
-        gnc_price_set_source_string (p, text);
-        g_free (text);
+        if (!apply_xmlnode_text (gnc_price_set_source_string, p, sub_node))
+            return FALSE;
     }
     else if (g_strcmp0 ("price:type", (char*)sub_node->name) == 0)
     {
-        char* text = dom_tree_to_text (sub_node);
-        if (!text) return FALSE;
-        gnc_price_set_typestr (p, text);
-        g_free (text);
+        if (!apply_xmlnode_text (gnc_price_set_typestr, p, sub_node))
+            return FALSE;
     }
     else if (g_strcmp0 ("price:value", (char*)sub_node->name) == 0)
     {
diff --git a/libgnucash/backend/xml/gnc-recurrence-xml-v2.cpp b/libgnucash/backend/xml/gnc-recurrence-xml-v2.cpp
index 7ffb68e314..76e8aa188f 100644
--- a/libgnucash/backend/xml/gnc-recurrence-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-recurrence-xml-v2.cpp
@@ -52,15 +52,13 @@ const gchar* recurrence_version_string = "1.0.0";
 static gboolean
 recurrence_period_type_handler (xmlNodePtr node, gpointer d)
 {
-    PeriodType pt;
-    char* nodeTxt;
-
-    nodeTxt = dom_tree_to_text (node);
-    g_return_val_if_fail (nodeTxt, FALSE);
-    pt = recurrencePeriodTypeFromString (nodeTxt);
-    ((Recurrence*) d)->ptype = pt;
-    g_free (nodeTxt);
-    return (pt != -1);
+    auto r = static_cast<Recurrence*>(d);
+    auto set_ptype = [](Recurrence *r, const char* txt)
+    {
+        r->ptype = recurrencePeriodTypeFromString (txt);
+    };
+    apply_xmlnode_text (set_ptype, r, node);
+    return (r->ptype != -1);
 }
 
 static gboolean
@@ -85,15 +83,13 @@ recurrence_mult_handler (xmlNodePtr node, gpointer r)
 static gboolean
 recurrence_weekend_adj_handler (xmlNodePtr node, gpointer d)
 {
-    WeekendAdjust wadj;
-    char* nodeTxt;
-
-    nodeTxt = dom_tree_to_text (node);
-    g_return_val_if_fail (nodeTxt, FALSE);
-    wadj = recurrenceWeekendAdjustFromString (nodeTxt);
-    ((Recurrence*) d)->wadj = wadj;
-    g_free (nodeTxt);
-    return (wadj != -1);
+    auto r = static_cast<Recurrence*>(d);
+    auto set_wadj = [](Recurrence *r, const char* txt)
+    {
+        r->wadj = recurrenceWeekendAdjustFromString (txt);
+    };
+    apply_xmlnode_text (set_wadj, r, node);
+    return (r->wadj != -1);
 }
 
 static struct dom_tree_handler recurrence_dom_handlers[] =
diff --git a/libgnucash/backend/xml/gnc-schedxaction-xml-v2.cpp b/libgnucash/backend/xml/gnc-schedxaction-xml-v2.cpp
index db76a4d1ba..e5e14a8a7c 100644
--- a/libgnucash/backend/xml/gnc-schedxaction-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-schedxaction-xml-v2.cpp
@@ -223,52 +223,40 @@ gboolean
 sx_name_handler (xmlNodePtr node, gpointer sx_pdata)
 {
     struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
-    SchedXaction* sx = pdata->sx;
-    gchar* tmp = dom_tree_to_text (node);
-    DEBUG ("sx named [%s]", tmp);
-    g_return_val_if_fail (tmp, FALSE);
-    xaccSchedXactionSetName (sx, tmp);
-    g_free (tmp);
-    return TRUE;
+    return apply_xmlnode_text (xaccSchedXactionSetName, pdata->sx, node);
 }
 
 static gboolean
 sx_enabled_handler (xmlNodePtr node, gpointer sx_pdata)
 {
     struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
-    SchedXaction* sx = pdata->sx;
-    gchar* tmp = dom_tree_to_text (node);
-
-    sx->enabled = (g_strcmp0 (tmp, "y") == 0 ? TRUE : FALSE);
-    g_free (tmp);
-
-    return TRUE;
+    auto set_enabled = [](SchedXaction* sx, const char* txt)
+    {
+        sx->enabled = !g_strcmp0 (txt, "y");
+    };
+    return apply_xmlnode_text (set_enabled, pdata->sx, node);
 }
 
 static gboolean
 sx_autoCreate_handler (xmlNodePtr node, gpointer sx_pdata)
 {
     struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
-    SchedXaction* sx = pdata->sx;
-    gchar* tmp = dom_tree_to_text (node);
-
-    sx->autoCreateOption = (g_strcmp0 (tmp, "y") == 0 ? TRUE : FALSE);
-    g_free (tmp);
-
-    return TRUE;
+    auto set_autocreate = [](SchedXaction* sx, const char* txt)
+    {
+        sx->autoCreateOption = !g_strcmp0 (txt, "y");
+    };
+    return apply_xmlnode_text (set_autocreate, pdata->sx, node);
 }
 
 static gboolean
 sx_notify_handler (xmlNodePtr node, gpointer sx_pdata)
 {
     struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
-    SchedXaction* sx = pdata->sx;
-    gchar* tmp = dom_tree_to_text (node);
-
-    sx->autoCreateNotify = (g_strcmp0 (tmp, "y") == 0 ? TRUE : FALSE);
-    g_free (tmp);
-
-    return TRUE;
+    auto set_notify = [](SchedXaction* sx, const char* txt)
+    {
+        sx->autoCreateNotify = !g_strcmp0 (txt, "y");
+    };
+    return apply_xmlnode_text (set_notify, pdata->sx, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-tax-table-xml-v2.cpp b/libgnucash/backend/xml/gnc-tax-table-xml-v2.cpp
index 25eb5756af..b0f12f6f24 100644
--- a/libgnucash/backend/xml/gnc-tax-table-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-tax-table-xml-v2.cpp
@@ -161,20 +161,13 @@ static gboolean
 ttentry_type_handler (xmlNodePtr node, gpointer ttentry_pdata)
 {
     struct ttentry_pdata* pdata = static_cast<decltype (pdata)> (ttentry_pdata);
-    GncAmountType type;
-    char* str;
-    gboolean ret;
-
-    str = dom_tree_to_text (node);
-    g_return_val_if_fail (str, FALSE);
-
-    ret = gncAmountStringToType (str, &type);
-    g_free (str);
-
-    if (ret)
-        gncTaxTableEntrySetType (pdata->ttentry, type);
-
-    return ret;
+    auto tte_settype = [](GncTaxTableEntry* tt, const char *str)
+    {
+        GncAmountType type;
+        if (gncAmountStringToType (str, &type))
+            gncTaxTableEntrySetType (tt, type);
+    };
+    return apply_xmlnode_text (tte_settype, pdata->ttentry, node);
 }
 
 static gboolean
@@ -281,12 +274,7 @@ static gboolean
 taxtable_name_handler (xmlNodePtr node, gpointer taxtable_pdata)
 {
     struct taxtable_pdata* pdata = static_cast<decltype (pdata)> (taxtable_pdata);
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    gncTaxTableSetName (pdata->table, txt);
-    g_free (txt);
-    return TRUE;
+    return apply_xmlnode_text (gncTaxTableSetName, pdata->table, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-transaction-xml-v2.cpp b/libgnucash/backend/xml/gnc-transaction-xml-v2.cpp
index db0d577b27..83c70f57e8 100644
--- a/libgnucash/backend/xml/gnc-transaction-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-transaction-xml-v2.cpp
@@ -199,26 +199,6 @@ struct split_pdata
     QofBook* book;
 };
 
-static inline gboolean
-set_spl_string (xmlNodePtr node, Split* spl,
-                void (*func) (Split* spl, const char* txt))
-{
-    if (auto txt = dom_node_to_text (node))
-    {
-        func (spl, txt);
-        return TRUE;
-    }
-
-    gchar* tmp = dom_tree_to_text (node);
-    g_return_val_if_fail (tmp, FALSE);
-
-    func (spl, tmp);
-
-    g_free (tmp);
-
-    return TRUE;
-}
-
 static inline gboolean
 set_spl_gnc_num (xmlNodePtr node, Split* spl,
                  void (*func) (Split* spl, gnc_numeric gn))
@@ -243,35 +223,25 @@ static gboolean
 spl_memo_handler (xmlNodePtr node, gpointer data)
 {
     struct split_pdata* pdata = static_cast<decltype (pdata)> (data);
-    return set_spl_string (node, pdata->split, xaccSplitSetMemo);
+    return apply_xmlnode_text (xaccSplitSetMemo, pdata->split, node);
 }
 
 static gboolean
 spl_action_handler (xmlNodePtr node, gpointer data)
 {
     struct split_pdata* pdata = static_cast<decltype (pdata)> (data);
-    return set_spl_string (node, pdata->split, xaccSplitSetAction);
+    return apply_xmlnode_text (xaccSplitSetAction, pdata->split, node);
 }
 
 static gboolean
 spl_reconciled_state_handler (xmlNodePtr node, gpointer data)
 {
     struct split_pdata* pdata = static_cast<decltype (pdata)> (data);
-
-    if (auto txt = dom_node_to_text (node))
+    auto set_reconciled = [](Split* s, const char *txt)
     {
-        xaccSplitSetReconcile (pdata->split, txt[0]);
-        return TRUE;
-    }
-
-    gchar* tmp = dom_tree_to_text (node);
-    g_return_val_if_fail (tmp, FALSE);
-
-    xaccSplitSetReconcile (pdata->split, tmp[0]);
-
-    g_free (tmp);
-
-    return TRUE;
+        xaccSplitSetReconcile(s, txt[0]);
+    };
+    return apply_xmlnode_text (set_reconciled, pdata->split, node);
 }
 
 static gboolean
@@ -408,29 +378,6 @@ struct trans_pdata
     QofBook* book;
 };
 
-static inline gboolean
-set_tran_string (xmlNodePtr node, Transaction* trn,
-                 void (*func) (Transaction* trn, const char* txt))
-{
-    if (auto txt = dom_node_to_text (node))
-    {
-        func (trn, txt);
-        return TRUE;
-    }
-
-    gchar* tmp;
-
-    tmp = dom_tree_to_text (node);
-
-    g_return_val_if_fail (tmp, FALSE);
-
-    func (trn, tmp);
-
-    g_free (tmp);
-
-    return TRUE;
-}
-
 static gboolean
 set_tran_time64 (xmlNodePtr node, Transaction * trn,
         void (*func) (Transaction *, time64))
@@ -474,7 +421,7 @@ trn_num_handler (xmlNodePtr node, gpointer trans_pdata)
     struct trans_pdata* pdata = static_cast<decltype (pdata)> (trans_pdata);
     Transaction* trn = pdata->trans;
 
-    return set_tran_string (node, trn, xaccTransSetNum);
+    return apply_xmlnode_text (xaccTransSetNum, trn, node);
 }
 
 static gboolean
@@ -501,7 +448,7 @@ trn_description_handler (xmlNodePtr node, gpointer trans_pdata)
     struct trans_pdata* pdata = static_cast<decltype (pdata)> (trans_pdata);
     Transaction* trn = pdata->trans;
 
-    return set_tran_string (node, trn, xaccTransSetDescription);
+    return apply_xmlnode_text (xaccTransSetDescription, trn, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/gnc-vendor-xml-v2.cpp b/libgnucash/backend/xml/gnc-vendor-xml-v2.cpp
index 57b5996d72..d8698eae3e 100644
--- a/libgnucash/backend/xml/gnc-vendor-xml-v2.cpp
+++ b/libgnucash/backend/xml/gnc-vendor-xml-v2.cpp
@@ -130,20 +130,6 @@ struct vendor_pdata
     QofBook* book;
 };
 
-static gboolean
-set_string (xmlNodePtr node, GncVendor* vendor,
-            void (*func) (GncVendor* vendor, const char* txt))
-{
-    char* txt = dom_tree_to_text (node);
-    g_return_val_if_fail (txt, FALSE);
-
-    func (vendor, txt);
-
-    g_free (txt);
-
-    return TRUE;
-}
-
 static gboolean
 set_boolean (xmlNodePtr node, GncVendor* vendor,
              void (*func) (GncVendor* vendor, gboolean b))
@@ -163,7 +149,7 @@ vendor_name_handler (xmlNodePtr node, gpointer vendor_pdata)
 {
     struct vendor_pdata* pdata = static_cast<decltype (pdata)> (vendor_pdata);
 
-    return set_string (node, pdata->vendor, gncVendorSetName);
+    return apply_xmlnode_text (gncVendorSetName, pdata->vendor, node);
 }
 
 static gboolean
@@ -194,7 +180,7 @@ vendor_id_handler (xmlNodePtr node, gpointer vendor_pdata)
 {
     struct vendor_pdata* pdata = static_cast<decltype (pdata)> (vendor_pdata);
 
-    return set_string (node, pdata->vendor, gncVendorSetID);
+    return apply_xmlnode_text (gncVendorSetID, pdata->vendor, node);
 }
 
 static gboolean
@@ -202,7 +188,7 @@ vendor_notes_handler (xmlNodePtr node, gpointer vendor_pdata)
 {
     struct vendor_pdata* pdata = static_cast<decltype (pdata)> (vendor_pdata);
 
-    return set_string (node, pdata->vendor, gncVendorSetNotes);
+    return apply_xmlnode_text (gncVendorSetNotes, pdata->vendor, node);
 }
 
 static gboolean
@@ -232,20 +218,13 @@ static gboolean
 vendor_taxincluded_handler (xmlNodePtr node, gpointer vendor_pdata)
 {
     struct vendor_pdata* pdata = static_cast<decltype (pdata)> (vendor_pdata);
-    GncTaxIncluded type;
-    char* str;
-    gboolean ret;
-
-    str = dom_tree_to_text (node);
-    g_return_val_if_fail (str, FALSE);
-
-    ret = gncTaxIncludedStringToType (str, &type);
-    g_free (str);
-
-    if (ret)
-        gncVendorSetTaxIncluded (pdata->vendor, type);
-
-    return ret;
+    auto set_taxincluded = [](GncVendor* vendor, const char* str)
+    {
+        GncTaxIncluded type;
+        if (gncTaxIncludedStringToType (str, &type))
+            gncVendorSetTaxIncluded (vendor, type);
+    };
+    return apply_xmlnode_text (set_taxincluded, pdata->vendor, node);
 }
 
 static gboolean
diff --git a/libgnucash/backend/xml/io-gncxml-v1.cpp b/libgnucash/backend/xml/io-gncxml-v1.cpp
index 51f50b2a05..8dd6ec7c66 100644
--- a/libgnucash/backend/xml/io-gncxml-v1.cpp
+++ b/libgnucash/backend/xml/io-gncxml-v1.cpp
@@ -2959,17 +2959,13 @@ price_parse_xml_sub_node (GNCPrice* p, xmlNodePtr sub_node, QofBook* book)
     }
     else if (g_strcmp0 ("price:source", (char*)sub_node->name) == 0)
     {
-        char* text = dom_tree_to_text (sub_node);
-        if (!text) return FALSE;
-        gnc_price_set_source_string (p, text);
-        g_free (text);
+        if (!apply_xmlnode_text (gnc_price_set_source_string, p, sub_node))
+            return false;
     }
     else if (g_strcmp0 ("price:type", (char*)sub_node->name) == 0)
     {
-        char* text = dom_tree_to_text (sub_node);
-        if (!text) return FALSE;
-        gnc_price_set_typestr (p, text);
-        g_free (text);
+        if (!apply_xmlnode_text (gnc_price_set_typestr, p, sub_node))
+            return false;
     }
     else if (g_strcmp0 ("price:value", (char*)sub_node->name) == 0)
     {
diff --git a/libgnucash/backend/xml/io-gncxml-v2.cpp b/libgnucash/backend/xml/io-gncxml-v2.cpp
index a13440fe8a..327dae7226 100644
--- a/libgnucash/backend/xml/io-gncxml-v2.cpp
+++ b/libgnucash/backend/xml/io-gncxml-v2.cpp
@@ -369,7 +369,6 @@ gnc_counter_end_handler (gpointer data_for_children,
                          gpointer parent_data, gpointer global_data,
                          gpointer* result, const gchar* tag)
 {
-    char* strval;
     gint64 val;
     char* type;
     xmlNodePtr tree = (xmlNodePtr)data_for_children;
@@ -392,9 +391,9 @@ gnc_counter_end_handler (gpointer data_for_children,
      * This is invalid xml because the namespace isn't declared in the
      * tag itself. This should be changed to 'type' at some point. */
     type = (char*)xmlGetProp (tree, BAD_CAST "cd:type");
-    strval = dom_tree_to_text (tree);
-    if (!string_to_gint64 (strval, &val))
+    if (!apply_xmlnode_text<bool> ([&val](auto txt){ return string_to_gint64 (txt, &val);}, tree))
     {
+        auto strval = dom_tree_to_text (tree);
         PERR ("string_to_gint64 failed with input: %s",
               strval ? strval : "(null)");
         ret = FALSE;
@@ -449,7 +448,6 @@ gnc_counter_end_handler (gpointer data_for_children,
         }
     }
 
-    g_free (strval);
     xmlFree (type);
     xmlFreeNode (tree);
     return ret;
diff --git a/libgnucash/backend/xml/sixtp-dom-parsers.cpp b/libgnucash/backend/xml/sixtp-dom-parsers.cpp
index 34d560190d..6b9dcaaa6b 100644
--- a/libgnucash/backend/xml/sixtp-dom-parsers.cpp
+++ b/libgnucash/backend/xml/sixtp-dom-parsers.cpp
@@ -47,74 +47,46 @@ dom_node_to_text (xmlNodePtr node) noexcept
 std::optional<GncGUID>
 dom_tree_to_guid (xmlNodePtr node)
 {
-    if (!node->properties)
-    {
+    auto type = xmlGetProp (node, BAD_CAST "type");
+    if (!type)
         return {};
-    }
 
-    if (strcmp ((char*) node->properties->name, "type") != 0)
-    {
-        PERR ("Unknown attribute for id tag: %s",
-              node->properties->name ?
-              (char*) node->properties->name : "(null)");
+    bool ok = !g_strcmp0 ((char*)type, "guid") || !g_strcmp0 ((char*)type, "new");
+
+    xmlFree (type);
+
+    if (!ok)
         return {};
-    }
 
+    auto extract_guid = [](auto str) -> std::optional<GncGUID>
     {
-        char* type;
+        if (GncGUID guid; string_to_guid (str, &guid))
+            return guid;
 
-        type = (char*)xmlNodeGetContent (node->properties->xmlAttrPropertyValue);
+        return {};
+    };
 
-        /* handle new and guid the same for the moment */
-        if ((g_strcmp0 ("guid", type) == 0) || (g_strcmp0 ("new", type) == 0))
-        {
-            GncGUID gid;
-            char* guid_str;
-
-            guid_str = (char*)xmlNodeGetContent (node->xmlChildrenNode);
-            string_to_guid (guid_str, &gid);
-            xmlFree (guid_str);
-            xmlFree (type);
-            return gid;
-        }
-        else
-        {
-            PERR ("Unknown type %s for attribute type for tag %s",
-                  type ? type : "(null)",
-                  node->properties->name ?
-                  (char*) node->properties->name : "(null)");
-            xmlFree (type);
-            return {};
-        }
-    }
+    return apply_xmlnode_text<std::optional<GncGUID>>(extract_guid, node);
 }
 
 static KvpValue*
 dom_tree_to_integer_kvp_value (xmlNodePtr node)
 {
-    gchar* text;
-    gint64 daint;
-    KvpValue* ret = NULL;
-
-    text = dom_tree_to_text (node);
-
-    if (string_to_gint64 (text, &daint))
+    auto node_to_int_kvp = [](auto txt) -> KvpValue*
     {
-        ret = new KvpValue {daint};
-    }
-    g_free (text);
+        if (gint64 daint; string_to_gint64 (txt, &daint))
+            return new KvpValue{daint};
 
-    return ret;
+        return nullptr;
+    };
+    return apply_xmlnode_text<KvpValue*> (node_to_int_kvp, node, nullptr);
 }
 
 template <typename T>
 static bool
 dom_tree_to_num (xmlNodePtr node, std::function<bool(const char*, T*)>string_to_num, T* num_ptr)
 {
-    auto text = dom_tree_to_text (node);
-    auto ret = string_to_num (text, num_ptr);
-    g_free (text);
-    return ret;
+    return apply_xmlnode_text<T>([&](auto txt){ return string_to_num (txt, num_ptr);}, node, false);
 }
 
 gboolean
@@ -138,46 +110,36 @@ dom_tree_to_guint (xmlNodePtr node, guint* i)
 gboolean
 dom_tree_to_boolean (xmlNodePtr node, gboolean* b)
 {
-    gchar* text;
-
-    text = dom_tree_to_text (node);
-    if (g_ascii_strncasecmp (text, "true", 4) == 0)
-    {
-        *b = TRUE;
-        g_free (text);
-        return TRUE;
-    }
-    else if (g_ascii_strncasecmp (text, "false", 5) == 0)
-    {
-        *b = FALSE;
-        g_free (text);
-        return TRUE;
-    }
-    else
+    auto set_bool = [b](auto text) -> gboolean
     {
-        *b = FALSE;
-        g_free (text);
-        return FALSE;
-    }
+        if (g_ascii_strncasecmp (text, "true", 4) == 0)
+        {
+            *b = TRUE;
+            return TRUE;
+        }
+        else if (g_ascii_strncasecmp (text, "false", 5) == 0)
+        {
+            *b = FALSE;
+            return TRUE;
+        }
+        else
+        {
+            *b = FALSE;
+            return FALSE;
+        }
+    };
+    return apply_xmlnode_text<gboolean> (set_bool, node);
 }
 
 static KvpValue*
 dom_tree_to_double_kvp_value (xmlNodePtr node)
 {
-    gchar* text;
-    double dadoub;
-    KvpValue* ret = NULL;
-
-    text = dom_tree_to_text (node);
-
-    if (string_to_double (text, &dadoub))
+    auto node_to_double_kvp = [](auto txt) -> KvpValue*
     {
-        ret = new KvpValue {dadoub};
-    }
-
-    g_free (text);
-
-    return ret;
+        if (double dadoub; string_to_double (txt, &dadoub)) return new KvpValue{dadoub};
+        return nullptr;
+    };
+    return apply_xmlnode_text<KvpValue*> (node_to_double_kvp, node, nullptr);
 }
 
 static KvpValue*
@@ -189,30 +151,18 @@ dom_tree_to_numeric_kvp_value (xmlNodePtr node)
 static KvpValue*
 dom_tree_to_string_kvp_value (xmlNodePtr node)
 {
-    const gchar* datext;
-    KvpValue* ret = NULL;
-
-    datext = dom_tree_to_text (node);
-    if (datext)
+    auto node_to_string_kvp = [](auto txt) -> KvpValue*
     {
-        ret = new KvpValue {datext};
-    }
-
-    return ret;
+        return new KvpValue {g_strdup (txt)};
+    };
+    return apply_xmlnode_text<KvpValue*> (node_to_string_kvp, node, nullptr);
 }
 
 static KvpValue*
 dom_tree_to_guid_kvp_value (xmlNodePtr node)
 {
-    KvpValue* ret = NULL;
-
     auto daguid = dom_tree_to_guid (node);
-    if (daguid)
-    {
-        ret = new KvpValue {guid_copy (&*daguid)};
-    }
-
-    return ret;
+    return daguid ? new KvpValue {guid_copy (&*daguid)} : nullptr;
 }
 
 static KvpValue*
@@ -225,19 +175,8 @@ dom_tree_to_time64_kvp_value (xmlNodePtr node)
 static KvpValue*
 dom_tree_to_gdate_kvp_value (xmlNodePtr node)
 {
-    GDate* date;
-    KvpValue* ret = NULL;
-
-    date = dom_tree_to_gdate (node);
-
-    if (date)
-    {
-        ret = new KvpValue {*date};
-    }
-
-    g_free (date);
-
-    return ret;
+    auto date = dom_tree_to_gdate (node);
+    return date ? new KvpValue {*date} : nullptr;
 }
 
 gboolean
@@ -314,17 +253,8 @@ dom_tree_to_list_kvp_value (xmlNodePtr node)
 static KvpValue*
 dom_tree_to_frame_kvp_value (xmlNodePtr node)
 {
-    KvpFrame* frame;
-    KvpValue* ret = NULL;
-
-    frame = dom_tree_to_kvp_frame (node);
-
-    if (frame)
-    {
-        ret = new KvpValue {frame};
-    }
-
-    return ret;
+    KvpFrame* frame = dom_tree_to_kvp_frame (node);
+    return frame ? new KvpValue {frame} : nullptr;
 }
 
 
@@ -354,22 +284,14 @@ static KvpValue*
 dom_tree_to_kvp_value (xmlNodePtr node)
 {
     xmlChar* xml_type;
-    gchar* type;
     struct kvp_val_converter* mark;
     KvpValue* ret = NULL;
 
     xml_type = xmlGetProp (node, BAD_CAST "type");
-    if (xml_type)
-    {
-        type = g_strdup ((char*) xml_type);
-        xmlFree (xml_type);
-    }
-    else
-        type = NULL;
 
     for (mark = val_converters; mark->tag; mark++)
     {
-        if (g_strcmp0 (type, mark->tag) == 0)
+        if (g_strcmp0 (reinterpret_cast<char*>(xml_type), mark->tag) == 0)
         {
             ret = (mark->converter) (node);
         }
@@ -380,7 +302,7 @@ dom_tree_to_kvp_value (xmlNodePtr node)
         /* FIXME: deal with unknown type tag here */
     }
 
-    g_free (type);
+    xmlFree (xml_type);
 
     return ret;
 }
@@ -400,12 +322,18 @@ dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame)
             xmlNodePtr mark2;
             gchar* key = NULL;
             KvpValue* val = NULL;
+            bool must_free{false};
 
             for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next)
             {
                 if (g_strcmp0 ((char*)mark2->name, "slot:key") == 0)
                 {
-                    key = dom_tree_to_text (mark2);
+                    key = const_cast<char*>(dom_node_to_text (mark2));
+                    if (!key)
+                    {
+                        key = dom_tree_to_text (mark2);
+                        must_free = true;
+                    }
                 }
                 else if (g_strcmp0 ((char*)mark2->name, "slot:value") == 0)
                 {
@@ -429,7 +357,8 @@ dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame)
                 {
                     /* FIXME: should put some error here */
                 }
-                g_free (key);
+                if (must_free)
+                    g_free (key);
             }
         }
     }
@@ -499,16 +428,12 @@ dom_tree_to_text (xmlNodePtr tree)
 gnc_numeric
 dom_tree_to_gnc_numeric (xmlNodePtr node)
 {
-    gchar* content = dom_tree_to_text (node);
-    if (!content)
-        return gnc_numeric_zero ();
-
-    gnc_numeric num = gnc_numeric_from_string (content);
-    if (gnc_numeric_check (num))
-        num = gnc_numeric_zero ();
-
-    g_free (content);
-    return num;
+    auto node_to_numeric = [](auto txt)
+    {
+        gnc_numeric num = gnc_numeric_from_string(txt);
+        return gnc_numeric_check (num) ? gnc_numeric_zero() : num;
+    };
+    return apply_xmlnode_text<gnc_numeric> (node_to_numeric, node, gnc_numeric_zero());
 }
 
 
@@ -544,18 +469,8 @@ dom_tree_to_time64 (xmlNodePtr node)
                 {
                     return INT64_MAX;
                 }
-                else
-                {
-                    gchar* content = dom_tree_to_text (n);
-                    if (!content)
-                    {
-                        return INT64_MAX;
-                    }
-
-                    ret = gnc_iso8601_to_time64_gmt (content);
-                    g_free (content);
-                    seen = TRUE;
-                }
+                seen = TRUE;
+                ret = apply_xmlnode_text<time64> (gnc_iso8601_to_time64_gmt, n, INT64_MAX);
             }
             break;
         default:
@@ -589,6 +504,14 @@ dom_tree_to_gdate (xmlNodePtr node)
     gboolean seen_date = FALSE;
     xmlNodePtr n;
 
+    auto try_setting_date = [&ret](const char *content) -> bool
+    {
+        gint year = 0, month = 0, day = 0;
+        if (sscanf (content, "%d-%d-%d", &year, &month, &day) != 3) return false;
+        g_date_set_dmy (&ret, day, static_cast<GDateMonth>(month), year);
+        return (g_date_valid (&ret));
+    };
+
     /* creates an invalid date */
     g_date_clear (&ret, 1);
 
@@ -602,33 +525,9 @@ dom_tree_to_gdate (xmlNodePtr node)
         case XML_ELEMENT_NODE:
             if (g_strcmp0 ("gdate", (char*)n->name) == 0)
             {
-                if (seen_date)
-                {
+                if (seen_date || !apply_xmlnode_text<bool> (try_setting_date, n))
                     return NULL;
-                }
-                else
-                {
-                    gchar* content = dom_tree_to_text (n);
-                    gint year, month, day;
-                    if (!content)
-                    {
-                        return NULL;
-                    }
-
-                    if (sscanf (content, "%d-%d-%d", &year, &month, &day) != 3)
-                    {
-                        g_free (content);
-                        return NULL;
-                    }
-                    g_free (content);
-                    seen_date = TRUE;
-                    g_date_set_dmy (&ret, day, static_cast<GDateMonth> (month), year);
-                    if (!g_date_valid (&ret))
-                    {
-                        PWARN ("invalid date");
-                        return NULL;
-                    }
-                }
+                seen_date = TRUE;
             }
             break;
         default:
@@ -652,6 +551,14 @@ struct CommodityRef
     std::string id;
 };
 
+std::string
+gnc_strstrip (std::string_view sv)
+{
+    while (!sv.empty () && g_ascii_isspace (sv.front())) sv.remove_prefix (1);
+    while (!sv.empty () && g_ascii_isspace (sv.back())) sv.remove_suffix (1);
+    return std::string (sv);
+}
+
 static std::optional<CommodityRef>
 parse_commodity_ref (xmlNodePtr node, QofBook* book)
 {
@@ -666,8 +573,8 @@ parse_commodity_ref (xmlNodePtr node, QofBook* book)
        are required, though for now, order is irrelevant. */
 
     CommodityRef rv;
-    gchar* space_str = NULL;
-    gchar* id_str = NULL;
+    bool space_set{false};
+    bool id_set{false};
     xmlNodePtr n;
 
     if (!node) return {};
@@ -683,29 +590,21 @@ parse_commodity_ref (xmlNodePtr node, QofBook* book)
         case XML_ELEMENT_NODE:
             if (g_strcmp0 ("cmdty:space", (char*)n->name) == 0)
             {
-                if (space_str)
+                if (space_set)
                 {
                     return {};
                 }
-                else
-                {
-                    gchar* content = dom_tree_to_text (n);
-                    if (!content) return {};
-                    space_str = content;
-                }
+                rv.space = apply_xmlnode_text<std::string> (gnc_strstrip, n);
+                space_set = true;
             }
             else if (g_strcmp0 ("cmdty:id", (char*)n->name) == 0)
             {
-                if (id_str)
+                if (id_set)
                 {
                     return {};
                 }
-                else
-                {
-                    gchar* content = dom_tree_to_text (n);
-                    if (!content) return {};
-                    id_str = content;
-                }
+                rv.id = apply_xmlnode_text<std::string> (gnc_strstrip, n);
+                id_set = true;
             }
             break;
         default:
@@ -714,17 +613,10 @@ parse_commodity_ref (xmlNodePtr node, QofBook* book)
             break;
         }
     }
-    if (space_str && id_str)
-    {
-        g_strstrip (space_str);
-        g_strstrip (id_str);
-        rv = {space_str, id_str};
-    }
-
-    g_free (space_str);
-    g_free (id_str);
+    if (space_set && id_set)
+        return rv;
 
-    return rv;
+    return {};
 }
 
 gnc_commodity*
diff --git a/libgnucash/backend/xml/sixtp-dom-parsers.h b/libgnucash/backend/xml/sixtp-dom-parsers.h
index 6a0d1de5bc..0538b4b68a 100644
--- a/libgnucash/backend/xml/sixtp-dom-parsers.h
+++ b/libgnucash/backend/xml/sixtp-dom-parsers.h
@@ -34,6 +34,8 @@
 
 std::optional<GncGUID> dom_tree_to_guid (xmlNodePtr node);
 
+std::string gnc_strstrip (std::string_view sv);
+
 gnc_commodity* dom_tree_to_commodity_ref (xmlNodePtr node, QofBook* book);
 gnc_commodity* dom_tree_to_commodity_ref_no_engine (xmlNodePtr node, QofBook*);
 

commit 753e4354561614c47784f6ee12c9d13a20bad5c2
Author: Christopher Lam <christopher.lck at gmail.com>
Date:   Tue Dec 9 17:15:58 2025 +0800

    [sixtp-dom-parsers.h] add templated functions for node text
    
    * apply_xmlnode_text (node, f, optional default_val)
      retrieves text; and calls and returns f(text)
      on failure (i.e. node==null || node has no text), it returns default_val
    
    * apply_xmlnode_text (node, obj, f)
      retrieves text; and calls and returns f(obj, text)
      on success, it will return true, on failure it returns false

diff --git a/libgnucash/backend/xml/sixtp-dom-parsers.h b/libgnucash/backend/xml/sixtp-dom-parsers.h
index 37083438d9..6a0d1de5bc 100644
--- a/libgnucash/backend/xml/sixtp-dom-parsers.h
+++ b/libgnucash/backend/xml/sixtp-dom-parsers.h
@@ -71,6 +71,40 @@ struct dom_tree_handler
     int gotten;
 };
 
+template <typename T, typename F,
+          std::enable_if_t<std::is_invocable_r_v<void, F, const char*>, int> = 0>
+inline T
+apply_xmlnode_text (F&& f, xmlNodePtr node, T default_val = T{})
+{
+    if (!node)
+        return default_val;
+
+    if (auto txt = dom_node_to_text(node))
+        return f(txt);
+
+    if (auto txt = dom_tree_to_text(node))
+    {
+        auto rv = f(txt);
+        g_free(txt);
+        return rv;
+    }
+
+    return default_val;
+}
+
+template <typename Obj, typename F,
+          std::enable_if_t<std::is_invocable_r_v<void, F, Obj*, const char*>, int> = 0>
+inline bool
+apply_xmlnode_text (F&& f, Obj* obj, xmlNodePtr node)
+{
+    auto set_str = [&](auto txt)
+    {
+        f (obj, txt);
+        return true;
+    };
+    return apply_xmlnode_text<bool> (set_str, node, false);
+}
+
 gboolean dom_tree_generic_parse (xmlNodePtr node,
                                  struct dom_tree_handler* handlers,
                                  gpointer data);



Summary of changes:
 libgnucash/backend/xml/gnc-account-xml-v2.cpp      |  26 +-
 libgnucash/backend/xml/gnc-address-xml-v2.cpp      |  29 +-
 libgnucash/backend/xml/gnc-bill-term-xml-v2.cpp    |  15 +-
 libgnucash/backend/xml/gnc-budget-xml-v2.cpp       |  15 +-
 libgnucash/backend/xml/gnc-commodity-xml-v2.cpp    |  13 +-
 libgnucash/backend/xml/gnc-customer-xml-v2.cpp     |  40 +--
 libgnucash/backend/xml/gnc-employee-xml-v2.cpp     |  22 +-
 libgnucash/backend/xml/gnc-entry-xml-v2.cpp        |  87 ++----
 libgnucash/backend/xml/gnc-freqspec-xml-v2.cpp     |   7 +-
 libgnucash/backend/xml/gnc-invoice-xml-v2.cpp      |  18 +-
 libgnucash/backend/xml/gnc-job-xml-v2.cpp          |  20 +-
 libgnucash/backend/xml/gnc-order-xml-v2.cpp        |  19 +-
 libgnucash/backend/xml/gnc-owner-xml-v2.cpp        |  31 +-
 libgnucash/backend/xml/gnc-pricedb-xml-v2.cpp      |  12 +-
 libgnucash/backend/xml/gnc-recurrence-xml-v2.cpp   |  32 +-
 libgnucash/backend/xml/gnc-schedxaction-xml-v2.cpp |  44 +--
 libgnucash/backend/xml/gnc-tax-table-xml-v2.cpp    |  28 +-
 libgnucash/backend/xml/gnc-transaction-xml-v2.cpp  |  69 +----
 libgnucash/backend/xml/gnc-vendor-xml-v2.cpp       |  41 +--
 libgnucash/backend/xml/io-example-account.cpp      |   4 +-
 libgnucash/backend/xml/io-gncxml-v1.cpp            |  12 +-
 libgnucash/backend/xml/io-gncxml-v2.cpp            |   8 +-
 libgnucash/backend/xml/sixtp-dom-parsers.cpp       | 322 +++++++--------------
 libgnucash/backend/xml/sixtp-dom-parsers.h         |  34 ++-
 .../backend/xml/test/test-dom-converters1.cpp      |   6 +-
 libgnucash/backend/xml/test/test-file-stuff.cpp    |  18 +-
 .../backend/xml/test/test-string-converters.cpp    |  10 +-
 libgnucash/backend/xml/test/test-xml-account.cpp   |  11 +-
 libgnucash/backend/xml/test/test-xml-commodity.cpp |  11 +-
 .../backend/xml/test/test-xml-transaction.cpp      |  12 +-
 30 files changed, 318 insertions(+), 698 deletions(-)



More information about the gnucash-changes mailing list