1 /*
2 * Copyright 2024-2025 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <stdbool.h> // bool, false
13 #include <stdio.h> // NULL
14
15 #include <libxml/tree.h> // xmlDoc, xmlNode, etc.
16 #include <libxml/xmlstring.h> // xmlChar
17
18 #include "crmcommon_private.h"
19
20 /*!
21 * \internal
22 * \brief Create a new XML comment belonging to a given document
23 *
24 * \param[in] doc Document that new comment will belong to
25 * \param[in] content Comment content
26 *
27 * \return Newly created XML comment (guaranteed not to be \c NULL)
28 */
29 xmlNode *
30 pcmk__xc_create(xmlDoc *doc, const char *content)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
31 {
32 xmlNode *node = NULL;
33
34 // Pacemaker typically assumes every xmlNode has a doc
35 pcmk__assert(doc != NULL);
36
37 node = xmlNewDocComment(doc, (const xmlChar *) content);
38 pcmk__mem_assert(node);
39 pcmk__xml_new_private_data(node);
40 return node;
41 }
42
43 /*!
44 * \internal
45 * \brief Check whether two comments have matching content (case-insensitive)
46 *
47 * \param[in] comment1 First comment node to compare
48 * \param[in] comment2 Second comment node to compare
49 *
50 * \return \c true if \p comment1 and \p comment2 have matching content (by
51 * case-insensitive string comparison), or \c false otherwise
52 */
53 bool
54 pcmk__xc_matches(const xmlNode *comment1, const xmlNode *comment2)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
55 {
56 pcmk__assert((comment1 != NULL) && (comment1->type == XML_COMMENT_NODE)
57 && (comment2 != NULL) && (comment2->type == XML_COMMENT_NODE));
58
59 return pcmk__str_eq((const char *) comment1->content,
60 (const char *) comment2->content, pcmk__str_casei);
61 }
62
63 /*!
64 * \internal
65 * \brief Find a comment with matching content among children of specified XML
66 *
67 * \param[in] parent XML whose children to search
68 * \param[in] search Comment whose content should be searched for
69 *
70 * \return Matching comment, or \c NULL if no match is found
71 */
72 static xmlNode *
73 match_xc_child(const xmlNode *parent, const xmlNode *search)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
74 {
75 pcmk__assert((search != NULL) && (search->type == XML_COMMENT_NODE));
76
77 for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
78 child = pcmk__xml_next(child)) {
79
80 if (child->type != XML_COMMENT_NODE) {
81 continue;
82 }
83
84 if (pcmk__xc_matches(child, search)) {
85 return child;
86 }
87 }
88
89 return NULL;
90 }
91
92 /*!
93 * \internal
94 * \brief Make one XML comment match another (in content)
95 *
96 * \param[in,out] parent If \p target is NULL and this is not, add or update
97 * comment child of this XML node that matches \p update
98 * \param[in,out] target If not NULL, update this XML comment node
99 * \param[in] update Make comment content match this (must not be NULL)
100 *
101 * \note At least one of \parent and \target must be non-NULL
102 */
103 void
104 pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
105 {
106 CRM_CHECK(update != NULL, return);
107 CRM_CHECK(update->type == XML_COMMENT_NODE, return);
108
109 if (target == NULL) {
110 target = match_xc_child(parent, update);
111 }
112
113 if (target == NULL) {
114 pcmk__xml_copy(parent, update);
115
116 } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
117 xmlFree(target->content);
118 target->content = xmlStrdup(update->content);
119 }
120 }