1 /*
2 * Copyright 2017-2024 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 #ifndef PCMK__XML_INTERNAL__H
11 #define PCMK__XML_INTERNAL__H
12
13 /*
14 * Internal-only wrappers for and extensions to libxml2 (libxslt)
15 */
16
17 #include <stdlib.h>
18 #include <stdint.h> // uint32_t
19 #include <stdio.h>
20 #include <string.h>
21
22 #include <crm/crm.h> /* transitively imports qblog.h */
23 #include <crm/common/output_internal.h>
24 #include <crm/common/xml_io_internal.h>
25 #include <crm/common/xml_names_internal.h> // PCMK__XE_PROMOTABLE_LEGACY
26
27 #include <libxml/relaxng.h>
28
29 /*!
30 * \brief Base for directing lib{xml2,xslt} log into standard libqb backend
31 *
32 * This macro implements the core of what can be needed for directing
33 * libxml2 or libxslt error messaging into standard, preconfigured
34 * libqb-backed log stream.
35 *
36 * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt)
37 * emits a single message by chunks (location is emitted separatedly from
38 * the message itself), so we have to take the effort to combine these
39 * chunks back to single message. Whether to do this or not is driven
40 * with \p dechunk toggle.
41 *
42 * The form of a macro was chosen for implicit deriving of __FILE__, etc.
43 * and also because static dechunking buffer should be differentiated per
44 * library (here we assume different functions referring to this macro
45 * will not ever be using both at once), preferably also per-library
46 * context of use to avoid clashes altogether.
47 *
48 * Note that we cannot use qb_logt, because callsite data have to be known
49 * at the moment of compilation, which it is not always the case -- xml_log
50 * (and unfortunately there's no clear explanation of the fail to compile).
51 *
52 * Also note that there's no explicit guard against said libraries producing
53 * never-newline-terminated chunks (which would just keep consuming memory),
54 * as it's quite improbable. Termination of the program in between the
55 * same-message chunks will raise a flag with valgrind and the likes, though.
56 *
57 * And lastly, regarding how dechunking combines with other non-message
58 * parameters -- for \p priority, most important running specification
59 * wins (possibly elevated to LOG_ERR in case of nonconformance with the
60 * newline-termination "protocol"), \p dechunk is expected to always be
61 * on once it was at the start, and the rest (\p postemit and \p prefix)
62 * are picked directly from the last chunk entry finalizing the message
63 * (also reasonable to always have it the same with all related entries).
64 *
65 * \param[in] priority Syslog priority for the message to be logged
66 * \param[in] dechunk Whether to dechunk new-line terminated message
67 * \param[in] postemit Code to be executed once message is sent out
68 * \param[in] prefix How to prefix the message or NULL for raw passing
69 * \param[in] fmt Format string as with printf-like functions
70 * \param[in] ap Variable argument list to supplement \p fmt format string
71 */
72 #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \
73 do { \
74 if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \
75 qb_log_from_external_source_va(__func__, __FILE__, (fmt), \
76 (priority), __LINE__, 0, (ap)); \
77 (void) (postemit); \
78 } else { \
79 int CXLB_len = 0; \
80 char *CXLB_buf = NULL; \
81 static int CXLB_buffer_len = 0; \
82 static char *CXLB_buffer = NULL; \
83 static uint8_t CXLB_priority = 0; \
84 \
85 CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \
86 \
87 if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \
88 if (CXLB_len < 0) { \
89 CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\
90 CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \
91 } else if (CXLB_len > 0 /* && (dechunk) */ \
92 && CXLB_buf[CXLB_len - 1] == '\n') { \
93 CXLB_buf[CXLB_len - 1] = '\0'; \
94 } \
95 if (CXLB_buffer) { \
96 qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \
97 CXLB_priority, __LINE__, 0, \
98 (prefix) != NULL ? (prefix) : "", \
99 CXLB_buffer, CXLB_buf); \
100 free(CXLB_buffer); \
101 } else { \
102 qb_log_from_external_source(__func__, __FILE__, "%s%s", \
103 (priority), __LINE__, 0, \
104 (prefix) != NULL ? (prefix) : "", \
105 CXLB_buf); \
106 } \
107 if (CXLB_len < 0) { \
108 CXLB_buf = NULL; /* restore temporary override */ \
109 } \
110 CXLB_buffer = NULL; \
111 CXLB_buffer_len = 0; \
112 (void) (postemit); \
113 \
114 } else if (CXLB_buffer == NULL) { \
115 CXLB_buffer_len = CXLB_len; \
116 CXLB_buffer = CXLB_buf; \
117 CXLB_buf = NULL; \
118 CXLB_priority = (priority); /* remember as a running severest */ \
119 \
120 } else { \
121 CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \
122 memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \
123 CXLB_buffer_len += CXLB_len; \
124 CXLB_buffer[CXLB_buffer_len] = '\0'; \
125 CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \
126 } \
127 free(CXLB_buf); \
128 } \
129 } while (0)
130
131 /*
132 * \enum pcmk__xml_fmt_options
133 * \brief Bit flags to control format in XML logs and dumps
134 */
135 enum pcmk__xml_fmt_options {
136 //! Exclude certain XML attributes (for calculating digests)
137 pcmk__xml_fmt_filtered = (1 << 0),
138
139 //! Include indentation and newlines
140 pcmk__xml_fmt_pretty = (1 << 1),
141
142 //! Include the opening tag of an XML element, and include XML comments
143 pcmk__xml_fmt_open = (1 << 3),
144
145 //! Include the children of an XML element
146 pcmk__xml_fmt_children = (1 << 4),
147
148 //! Include the closing tag of an XML element
149 pcmk__xml_fmt_close = (1 << 5),
150
151 // @COMPAT Can we start including text nodes unconditionally?
152 //! Include XML text nodes
153 pcmk__xml_fmt_text = (1 << 6),
154
155 // @COMPAT Remove when v1 patchsets are removed
156 //! Log a created XML subtree
157 pcmk__xml_fmt_diff_plus = (1 << 7),
158
159 // @COMPAT Remove when v1 patchsets are removed
160 //! Log a removed XML subtree
161 pcmk__xml_fmt_diff_minus = (1 << 8),
162
163 // @COMPAT Remove when v1 patchsets are removed
164 //! Log a minimal version of an XML diff (only showing the changes)
165 pcmk__xml_fmt_diff_short = (1 << 9),
166 };
167
168 int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
169 int depth, uint32_t options);
170 int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml);
171
172 /* XML search strings for guest, remote and pacemaker_remote nodes */
173
174 /* search string to find CIB resources entries for cluster nodes */
175 #define PCMK__XP_MEMBER_NODE_CONFIG \
176 "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_NODES \
177 "/" PCMK_XE_NODE \
178 "[not(@" PCMK_XA_TYPE ") or @" PCMK_XA_TYPE "='" PCMK_VALUE_MEMBER "']"
179
180 /* search string to find CIB resources entries for guest nodes */
181 #define PCMK__XP_GUEST_NODE_CONFIG \
182 "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \
183 "//" PCMK_XE_META_ATTRIBUTES "//" PCMK_XE_NVPAIR \
184 "[@" PCMK_XA_NAME "='" PCMK_META_REMOTE_NODE "']"
185
186 /* search string to find CIB resources entries for remote nodes */
187 #define PCMK__XP_REMOTE_NODE_CONFIG \
188 "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \
189 "[@" PCMK_XA_TYPE "='" PCMK_VALUE_REMOTE "']" \
190 "[@" PCMK_XA_PROVIDER "='pacemaker']"
191
192 /* search string to find CIB node status entries for pacemaker_remote nodes */
193 #define PCMK__XP_REMOTE_NODE_STATUS \
194 "//" PCMK_XE_CIB "//" PCMK_XE_STATUS "//" PCMK__XE_NODE_STATE \
195 "[@" PCMK_XA_REMOTE_NODE "='" PCMK_VALUE_TRUE "']"
196 /*!
197 * \internal
198 * \brief Serialize XML (using libxml) into provided descriptor
199 *
200 * \param[in] fd File descriptor to (piece-wise) write to
201 * \param[in] cur XML subtree to proceed
202 *
203 * \return a standard Pacemaker return code
204 */
205 int pcmk__xml2fd(int fd, xmlNode *cur);
206
207 enum pcmk__xml_artefact_ns {
208 pcmk__xml_artefact_ns_legacy_rng = 1,
209 pcmk__xml_artefact_ns_legacy_xslt,
210 pcmk__xml_artefact_ns_base_rng,
211 pcmk__xml_artefact_ns_base_xslt,
212 };
213
214 void pcmk__strip_xml_text(xmlNode *xml);
215 const char *pcmk__xe_add_last_written(xmlNode *xe);
216
217 xmlNode *pcmk__xe_first_child(const xmlNode *parent, const char *node_name,
218 const char *attr_n, const char *attr_v);
219
220
221 void pcmk__xe_remove_attr(xmlNode *element, const char *name);
222 bool pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data);
223 void pcmk__xe_remove_matching_attrs(xmlNode *element,
224 bool (*match)(xmlAttrPtr, void *),
225 void *user_data);
226 int pcmk__xe_delete_match(xmlNode *xml, xmlNode *search);
227 int pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace);
228 int pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags);
229
230 GString *pcmk__element_xpath(const xmlNode *xml);
231
232 /*!
233 * \internal
234 * \enum pcmk__xml_escape_type
235 * \brief Indicators of which XML characters to escape
236 *
237 * XML allows the escaping of special characters by replacing them with entity
238 * references (for example, <tt>"""</tt>) or character references (for
239 * example, <tt>" "</tt>).
240 *
241 * The special characters <tt>'&'</tt> (except as the beginning of an entity
242 * reference) and <tt>'<'</tt> are not allowed in their literal forms in XML
243 * character data. Character data is non-markup text (for example, the content
244 * of a text node). <tt>'>'</tt> is allowed under most circumstances; we escape
245 * it for safety and symmetry.
246 *
247 * For more details, see the "Character Data and Markup" section of the XML
248 * spec, currently section 2.4:
249 * https://www.w3.org/TR/xml/#dt-markup
250 *
251 * Attribute values are handled specially.
252 * * If an attribute value is delimited by single quotes, then single quotes
253 * must be escaped within the value.
254 * * Similarly, if an attribute value is delimited by double quotes, then double
255 * quotes must be escaped within the value.
256 * * A conformant XML processor replaces a literal whitespace character (tab,
257 * newline, carriage return, space) in an attribute value with a space
258 * (\c '#x20') character. However, a reference to a whitespace character (for
259 * example, \c "
" for \c '\n') does not get replaced.
260 * * For more details, see the "Attribute-Value Normalization" section of the
261 * XML spec, currently section 3.3.3. Note that the default attribute type
262 * is CDATA; we don't deal with NMTOKENS, etc.:
263 * https://www.w3.org/TR/xml/#AVNormalize
264 *
265 * Pacemaker always delimits attribute values with double quotes, so there's no
266 * need to escape single quotes.
267 *
268 * Newlines and tabs should be escaped in attribute values when XML is
269 * serialized to text, so that future parsing preserves them rather than
270 * normalizing them to spaces.
271 *
272 * We always escape carriage returns, so that they're not converted to spaces
273 * during attribute-value normalization and because displaying them as literals
274 * is messy.
275 */
276 enum pcmk__xml_escape_type {
277 /*!
278 * For text nodes.
279 * * Escape \c '<', \c '>', and \c '&' using entity references.
280 * * Do not escape \c '\n' and \c '\t'.
281 * * Escape other non-printing characters using character references.
282 */
283 pcmk__xml_escape_text,
284
285 /*!
286 * For attribute values.
287 * * Escape \c '<', \c '>', \c '&', and \c '"' using entity references.
288 * * Escape \c '\n', \c '\t', and other non-printing characters using
289 * character references.
290 */
291 pcmk__xml_escape_attr,
292
293 /* @COMPAT Drop escaping of at least '\n' and '\t' for
294 * pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip,
295 * and openstack-virtual-ip resource agents no longer depend on it.
296 *
297 * At time of writing, openstack-info may set a multiline value for the
298 * openstack_ports node attribute. The other two agents query the value and
299 * require it to be on one line with no spaces.
300 */
301 /*!
302 * For attribute values displayed in text output delimited by double quotes.
303 * * Escape \c '\n' as \c "\\n"
304 * * Escape \c '\r' as \c "\\r"
305 * * Escape \c '\t' as \c "\\t"
306 * * Escape \c '"' as \c "\\""
307 */
308 pcmk__xml_escape_attr_pretty,
309 };
310
311 bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type);
312 char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type);
313
314 /*!
315 * \internal
316 * \brief Get the root directory to scan XML artefacts of given kind for
317 *
318 * \param[in] ns governs the hierarchy nesting against the inherent root dir
319 *
320 * \return root directory to scan XML artefacts of given kind for
321 */
322 char *
323 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
324
325 /*!
326 * \internal
327 * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
328 *
329 * \param[in] ns denotes path forming details (parent dir, suffix)
330 * \param[in] filespec symbolic file specification to be combined with
331 * #artefact_ns to form the final path
332 * \return unwrapped path to particular XML artifact (RNG/XSLT)
333 */
334 char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
335 const char *filespec);
336
337 /*!
338 * \internal
339 * \brief Retrieve the value of the \c PCMK_XA_ID XML attribute
340 *
341 * \param[in] xml XML element to check
342 *
343 * \return Value of the \c PCMK_XA_ID attribute (may be \c NULL)
344 */
345 static inline const char *
346 pcmk__xe_id(const xmlNode *xml)
/* ![[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)
*/
347 {
348 return crm_element_value(xml, PCMK_XA_ID);
349 }
350
351 /*!
352 * \internal
353 * \brief Check whether an XML element is of a particular type
354 *
355 * \param[in] xml XML element to compare
356 * \param[in] name XML element name to compare
357 *
358 * \return \c true if \p xml is of type \p name, otherwise \c false
359 */
360 static inline bool
361 pcmk__xe_is(const xmlNode *xml, const char *name)
/* ![[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)
*/
362 {
363 return (xml != NULL) && (xml->name != NULL) && (name != NULL)
364 && (strcmp((const char *) xml->name, name) == 0);
365 }
366
367 /*!
368 * \internal
369 * \brief Return first non-text child node of an XML node
370 *
371 * \param[in] parent XML node to check
372 *
373 * \return First non-text child node of \p parent (or NULL if none)
374 */
375 static inline xmlNode *
376 pcmk__xml_first_child(const xmlNode *parent)
/* ![[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)
*/
377 {
378 xmlNode *child = (parent? parent->children : NULL);
379
380 while (child && (child->type == XML_TEXT_NODE)) {
381 child = child->next;
382 }
383 return child;
384 }
385
386 /*!
387 * \internal
388 * \brief Return next non-text sibling node of an XML node
389 *
390 * \param[in] child XML node to check
391 *
392 * \return Next non-text sibling of \p child (or NULL if none)
393 */
394 static inline xmlNode *
395 pcmk__xml_next(const xmlNode *child)
/* ![[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)
*/
396 {
397 xmlNode *next = (child? child->next : NULL);
398
399 while (next && (next->type == XML_TEXT_NODE)) {
400 next = next->next;
401 }
402 return next;
403 }
404
405 /*!
406 * \internal
407 * \brief Return next non-text sibling element of an XML element
408 *
409 * \param[in] child XML element to check
410 *
411 * \return Next sibling element of \p child (or NULL if none)
412 */
413 static inline xmlNode *
414 pcmk__xe_next(const xmlNode *child)
/* ![[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)
*/
415 {
416 xmlNode *next = child? child->next : NULL;
417
418 while (next && (next->type != XML_ELEMENT_NODE)) {
419 next = next->next;
420 }
421 return next;
422 }
423
424 xmlNode *pcmk__xe_create(xmlNode *parent, const char *name);
425 xmlNode *pcmk__xc_create(xmlDoc *doc, const char *content);
426 void pcmk__xml_free_doc(xmlDoc *doc);
427 xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src);
428 xmlNode *pcmk__xe_next_same(const xmlNode *node);
429
430 void pcmk__xe_set_content(xmlNode *node, const char *format, ...)
/* ![[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)
*/
431 G_GNUC_PRINTF(2, 3);
432
433 /*!
434 * \internal
435 * \enum pcmk__xa_flags
436 * \brief Flags for operations affecting XML attributes
437 */
438 enum pcmk__xa_flags {
439 //! Flag has no effect
440 pcmk__xaf_none = 0U,
441
442 //! Don't overwrite existing values
443 pcmk__xaf_no_overwrite = (1U << 0),
444
445 /*!
446 * Treat values as score updates where possible (see
447 * \c pcmk__xe_set_score())
448 */
449 pcmk__xaf_score_update = (1U << 1),
450 };
451
452 int pcmk__xe_get_score(const xmlNode *xml, const char *name, int *score,
453 int default_score);
454
455 int pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags);
456
457 /*!
458 * \internal
459 * \brief Like pcmk__xe_set_props, but takes a va_list instead of
460 * arguments directly.
461 *
462 * \param[in,out] node XML to add attributes to
463 * \param[in] pairs NULL-terminated list of name/value pairs to add
464 */
465 void
466 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
467
468 /*!
469 * \internal
470 * \brief Add a NULL-terminated list of name/value pairs to the given
471 * XML node as properties.
472 *
473 * \param[in,out] node XML node to add properties to
474 * \param[in] ... NULL-terminated list of name/value pairs
475 *
476 * \note A NULL name terminates the arguments; a NULL value will be skipped.
477 */
478 void
479 pcmk__xe_set_props(xmlNodePtr node, ...)
/* ![[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)
*/
480 G_GNUC_NULL_TERMINATED;
481
482 /*!
483 * \internal
484 * \brief Get first attribute of an XML element
485 *
486 * \param[in] xe XML element to check
487 *
488 * \return First attribute of \p xe (or NULL if \p xe is NULL or has none)
489 */
490 static inline xmlAttr *
491 pcmk__xe_first_attr(const xmlNode *xe)
492 {
493 return (xe == NULL)? NULL : xe->properties;
494 }
495
496 /*!
497 * \internal
498 * \brief Extract the ID attribute from an XML element
499 *
500 * \param[in] xpath String to search
501 * \param[in] node Node to get the ID for
502 *
503 * \return ID attribute of \p node in xpath string \p xpath
504 */
505 char *
506 pcmk__xpath_node_id(const char *xpath, const char *node);
507
508 /*!
509 * \internal
510 * \brief Print an informational message if an xpath query returned multiple
511 * items with the same ID.
512 *
513 * \param[in,out] out The output object
514 * \param[in] search The xpath search result, most typically the result of
515 * calling cib->cmds->query().
516 * \param[in] name The name searched for
517 */
518 void
519 pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search,
520 const char *name);
521
522 /* internal XML-related utilities */
523
524 enum xml_private_flags {
525 pcmk__xf_none = 0x0000,
526 pcmk__xf_dirty = 0x0001,
527 pcmk__xf_deleted = 0x0002,
528 pcmk__xf_created = 0x0004,
529 pcmk__xf_modified = 0x0008,
530
531 pcmk__xf_tracking = 0x0010,
532 pcmk__xf_processed = 0x0020,
533 pcmk__xf_skip = 0x0040,
534 pcmk__xf_moved = 0x0080,
535
536 pcmk__xf_acl_enabled = 0x0100,
537 pcmk__xf_acl_read = 0x0200,
538 pcmk__xf_acl_write = 0x0400,
539 pcmk__xf_acl_deny = 0x0800,
540
541 pcmk__xf_acl_create = 0x1000,
542 pcmk__xf_acl_denied = 0x2000,
543 pcmk__xf_lazy = 0x4000,
544 };
545
546 void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag);
547
548 /*!
549 * \internal
550 * \brief Iterate over child elements of \p xml
551 *
552 * This function iterates over the children of \p xml, performing the
553 * callback function \p handler on each node. If the callback returns
554 * a value other than pcmk_rc_ok, the iteration stops and the value is
555 * returned. It is therefore possible that not all children will be
556 * visited.
557 *
558 * \param[in,out] xml The starting XML node. Can be NULL.
559 * \param[in] child_element_name The name that the node must match in order
560 * for \p handler to be run. If NULL, all
561 * child elements will match.
562 * \param[in] handler The callback function.
563 * \param[in,out] userdata User data to pass to the callback function.
564 * Can be NULL.
565 *
566 * \return Standard Pacemaker return code
567 */
568 int
569 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
570 int (*handler)(xmlNode *xml, void *userdata),
571 void *userdata);
572
573 bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
574 void *user_data);
575
576 static inline const char *
577 pcmk__xml_attr_value(const xmlAttr *attr)
/* ![[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)
*/
578 {
579 return ((attr == NULL) || (attr->children == NULL))? NULL
580 : (const char *) attr->children->content;
581 }
582
583 // @COMPAT Remove when v1 patchsets are removed
584 xmlNode *pcmk__diff_v1_xml_object(xmlNode *left, xmlNode *right, bool suppress);
585
586 // @COMPAT Drop when PCMK__XE_PROMOTABLE_LEGACY is removed
587 static inline const char *
588 pcmk__map_element_name(const xmlNode *xml)
/* ![[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)
*/
589 {
590 if (xml == NULL) {
591 return NULL;
592 } else if (pcmk__xe_is(xml, PCMK__XE_PROMOTABLE_LEGACY)) {
593 return PCMK_XE_CLONE;
594 } else {
595 return (const char *) xml->name;
596 }
597 }
598
599 #endif // PCMK__XML_INTERNAL__H