Skip to main content

rupg_types/xml/
build.rs

1//! The XML text that the SQL/XML functions of `xml.c` build: the names of elements and attributes from SQL names, the escapes of text, and the declarations of `XMLCONCAT` and `XMLROOT`.
2
3use rupg_common::SqlState;
4
5use super::{parse_xml_decl, print_xml_decl};
6use crate::TypeError;
7
8/// The code points that `is_valid_xml_namefirst` takes: `Letter`, `_` and `:` of XML 1.0, fourth edition, as the tables of libxml2 have them.
9const NAME_FIRST: &[(u32, u32)] = &[
10    (0x003A, 0x003A),
11    (0x0041, 0x005A),
12    (0x005F, 0x005F),
13    (0x0061, 0x007A),
14    (0x00C0, 0x00D6),
15    (0x00D8, 0x00F6),
16    (0x00F8, 0x0131),
17    (0x0134, 0x013E),
18    (0x0141, 0x0148),
19    (0x014A, 0x017E),
20    (0x0180, 0x01C3),
21    (0x01CD, 0x01F0),
22    (0x01F4, 0x01F5),
23    (0x01FA, 0x0217),
24    (0x0250, 0x02A8),
25    (0x02BB, 0x02C1),
26    (0x0386, 0x0386),
27    (0x0388, 0x038A),
28    (0x038C, 0x038C),
29    (0x038E, 0x03A1),
30    (0x03A3, 0x03CE),
31    (0x03D0, 0x03D6),
32    (0x03DA, 0x03DA),
33    (0x03DC, 0x03DC),
34    (0x03DE, 0x03DE),
35    (0x03E0, 0x03E0),
36    (0x03E2, 0x03F3),
37    (0x0401, 0x040C),
38    (0x040E, 0x044F),
39    (0x0451, 0x045C),
40    (0x045E, 0x0481),
41    (0x0490, 0x04C4),
42    (0x04C7, 0x04C8),
43    (0x04CB, 0x04CC),
44    (0x04D0, 0x04EB),
45    (0x04EE, 0x04F5),
46    (0x04F8, 0x04F9),
47    (0x0531, 0x0556),
48    (0x0559, 0x0559),
49    (0x0561, 0x0586),
50    (0x05D0, 0x05EA),
51    (0x05F0, 0x05F2),
52    (0x0621, 0x063A),
53    (0x0641, 0x064A),
54    (0x0671, 0x06B7),
55    (0x06BA, 0x06BE),
56    (0x06C0, 0x06CE),
57    (0x06D0, 0x06D3),
58    (0x06D5, 0x06D5),
59    (0x06E5, 0x06E6),
60    (0x0905, 0x0939),
61    (0x093D, 0x093D),
62    (0x0958, 0x0961),
63    (0x0985, 0x098C),
64    (0x098F, 0x0990),
65    (0x0993, 0x09A8),
66    (0x09AA, 0x09B0),
67    (0x09B2, 0x09B2),
68    (0x09B6, 0x09B9),
69    (0x09DC, 0x09DD),
70    (0x09DF, 0x09E1),
71    (0x09F0, 0x09F1),
72    (0x0A05, 0x0A0A),
73    (0x0A0F, 0x0A10),
74    (0x0A13, 0x0A28),
75    (0x0A2A, 0x0A30),
76    (0x0A32, 0x0A33),
77    (0x0A35, 0x0A36),
78    (0x0A38, 0x0A39),
79    (0x0A59, 0x0A5C),
80    (0x0A5E, 0x0A5E),
81    (0x0A72, 0x0A74),
82    (0x0A85, 0x0A8B),
83    (0x0A8D, 0x0A8D),
84    (0x0A8F, 0x0A91),
85    (0x0A93, 0x0AA8),
86    (0x0AAA, 0x0AB0),
87    (0x0AB2, 0x0AB3),
88    (0x0AB5, 0x0AB9),
89    (0x0ABD, 0x0ABD),
90    (0x0AE0, 0x0AE0),
91    (0x0B05, 0x0B0C),
92    (0x0B0F, 0x0B10),
93    (0x0B13, 0x0B28),
94    (0x0B2A, 0x0B30),
95    (0x0B32, 0x0B33),
96    (0x0B36, 0x0B39),
97    (0x0B3D, 0x0B3D),
98    (0x0B5C, 0x0B5D),
99    (0x0B5F, 0x0B61),
100    (0x0B85, 0x0B8A),
101    (0x0B8E, 0x0B90),
102    (0x0B92, 0x0B95),
103    (0x0B99, 0x0B9A),
104    (0x0B9C, 0x0B9C),
105    (0x0B9E, 0x0B9F),
106    (0x0BA3, 0x0BA4),
107    (0x0BA8, 0x0BAA),
108    (0x0BAE, 0x0BB5),
109    (0x0BB7, 0x0BB9),
110    (0x0C05, 0x0C0C),
111    (0x0C0E, 0x0C10),
112    (0x0C12, 0x0C28),
113    (0x0C2A, 0x0C33),
114    (0x0C35, 0x0C39),
115    (0x0C60, 0x0C61),
116    (0x0C85, 0x0C8C),
117    (0x0C8E, 0x0C90),
118    (0x0C92, 0x0CA8),
119    (0x0CAA, 0x0CB3),
120    (0x0CB5, 0x0CB9),
121    (0x0CDE, 0x0CDE),
122    (0x0CE0, 0x0CE1),
123    (0x0D05, 0x0D0C),
124    (0x0D0E, 0x0D10),
125    (0x0D12, 0x0D28),
126    (0x0D2A, 0x0D39),
127    (0x0D60, 0x0D61),
128    (0x0E01, 0x0E2E),
129    (0x0E30, 0x0E30),
130    (0x0E32, 0x0E33),
131    (0x0E40, 0x0E45),
132    (0x0E81, 0x0E82),
133    (0x0E84, 0x0E84),
134    (0x0E87, 0x0E88),
135    (0x0E8A, 0x0E8A),
136    (0x0E8D, 0x0E8D),
137    (0x0E94, 0x0E97),
138    (0x0E99, 0x0E9F),
139    (0x0EA1, 0x0EA3),
140    (0x0EA5, 0x0EA5),
141    (0x0EA7, 0x0EA7),
142    (0x0EAA, 0x0EAB),
143    (0x0EAD, 0x0EAE),
144    (0x0EB0, 0x0EB0),
145    (0x0EB2, 0x0EB3),
146    (0x0EBD, 0x0EBD),
147    (0x0EC0, 0x0EC4),
148    (0x0F40, 0x0F47),
149    (0x0F49, 0x0F69),
150    (0x10A0, 0x10C5),
151    (0x10D0, 0x10F6),
152    (0x1100, 0x1100),
153    (0x1102, 0x1103),
154    (0x1105, 0x1107),
155    (0x1109, 0x1109),
156    (0x110B, 0x110C),
157    (0x110E, 0x1112),
158    (0x113C, 0x113C),
159    (0x113E, 0x113E),
160    (0x1140, 0x1140),
161    (0x114C, 0x114C),
162    (0x114E, 0x114E),
163    (0x1150, 0x1150),
164    (0x1154, 0x1155),
165    (0x1159, 0x1159),
166    (0x115F, 0x1161),
167    (0x1163, 0x1163),
168    (0x1165, 0x1165),
169    (0x1167, 0x1167),
170    (0x1169, 0x1169),
171    (0x116D, 0x116E),
172    (0x1172, 0x1173),
173    (0x1175, 0x1175),
174    (0x119E, 0x119E),
175    (0x11A8, 0x11A8),
176    (0x11AB, 0x11AB),
177    (0x11AE, 0x11AF),
178    (0x11B7, 0x11B8),
179    (0x11BA, 0x11BA),
180    (0x11BC, 0x11C2),
181    (0x11EB, 0x11EB),
182    (0x11F0, 0x11F0),
183    (0x11F9, 0x11F9),
184    (0x1E00, 0x1E9B),
185    (0x1EA0, 0x1EF9),
186    (0x1F00, 0x1F15),
187    (0x1F18, 0x1F1D),
188    (0x1F20, 0x1F45),
189    (0x1F48, 0x1F4D),
190    (0x1F50, 0x1F57),
191    (0x1F59, 0x1F59),
192    (0x1F5B, 0x1F5B),
193    (0x1F5D, 0x1F5D),
194    (0x1F5F, 0x1F7D),
195    (0x1F80, 0x1FB4),
196    (0x1FB6, 0x1FBC),
197    (0x1FBE, 0x1FBE),
198    (0x1FC2, 0x1FC4),
199    (0x1FC6, 0x1FCC),
200    (0x1FD0, 0x1FD3),
201    (0x1FD6, 0x1FDB),
202    (0x1FE0, 0x1FEC),
203    (0x1FF2, 0x1FF4),
204    (0x1FF6, 0x1FFC),
205    (0x2126, 0x2126),
206    (0x212A, 0x212B),
207    (0x212E, 0x212E),
208    (0x2180, 0x2182),
209    (0x3007, 0x3007),
210    (0x3021, 0x3029),
211    (0x3041, 0x3094),
212    (0x30A1, 0x30FA),
213    (0x3105, 0x312C),
214    (0x4E00, 0x9FA5),
215    (0xAC00, 0xD7A3),
216];
217/// The code points that `is_valid_xml_namechar` takes: `Letter`, `Digit`, `.`, `-`, `_`, `:`, `CombiningChar` and `Extender` of XML 1.0, fourth edition.
218const NAME_CHAR: &[(u32, u32)] = &[
219    (0x002D, 0x002E),
220    (0x0030, 0x003A),
221    (0x0041, 0x005A),
222    (0x005F, 0x005F),
223    (0x0061, 0x007A),
224    (0x00B7, 0x00B7),
225    (0x00C0, 0x00D6),
226    (0x00D8, 0x00F6),
227    (0x00F8, 0x0131),
228    (0x0134, 0x013E),
229    (0x0141, 0x0148),
230    (0x014A, 0x017E),
231    (0x0180, 0x01C3),
232    (0x01CD, 0x01F0),
233    (0x01F4, 0x01F5),
234    (0x01FA, 0x0217),
235    (0x0250, 0x02A8),
236    (0x02BB, 0x02C1),
237    (0x02D0, 0x02D1),
238    (0x0300, 0x0345),
239    (0x0360, 0x0361),
240    (0x0386, 0x038A),
241    (0x038C, 0x038C),
242    (0x038E, 0x03A1),
243    (0x03A3, 0x03CE),
244    (0x03D0, 0x03D6),
245    (0x03DA, 0x03DA),
246    (0x03DC, 0x03DC),
247    (0x03DE, 0x03DE),
248    (0x03E0, 0x03E0),
249    (0x03E2, 0x03F3),
250    (0x0401, 0x040C),
251    (0x040E, 0x044F),
252    (0x0451, 0x045C),
253    (0x045E, 0x0481),
254    (0x0483, 0x0486),
255    (0x0490, 0x04C4),
256    (0x04C7, 0x04C8),
257    (0x04CB, 0x04CC),
258    (0x04D0, 0x04EB),
259    (0x04EE, 0x04F5),
260    (0x04F8, 0x04F9),
261    (0x0531, 0x0556),
262    (0x0559, 0x0559),
263    (0x0561, 0x0586),
264    (0x0591, 0x05A1),
265    (0x05A3, 0x05B9),
266    (0x05BB, 0x05BD),
267    (0x05BF, 0x05BF),
268    (0x05C1, 0x05C2),
269    (0x05C4, 0x05C4),
270    (0x05D0, 0x05EA),
271    (0x05F0, 0x05F2),
272    (0x0621, 0x063A),
273    (0x0640, 0x0652),
274    (0x0660, 0x0669),
275    (0x0670, 0x06B7),
276    (0x06BA, 0x06BE),
277    (0x06C0, 0x06CE),
278    (0x06D0, 0x06D3),
279    (0x06D5, 0x06E8),
280    (0x06EA, 0x06ED),
281    (0x06F0, 0x06F9),
282    (0x0901, 0x0903),
283    (0x0905, 0x0939),
284    (0x093C, 0x094D),
285    (0x0951, 0x0954),
286    (0x0958, 0x0963),
287    (0x0966, 0x096F),
288    (0x0981, 0x0983),
289    (0x0985, 0x098C),
290    (0x098F, 0x0990),
291    (0x0993, 0x09A8),
292    (0x09AA, 0x09B0),
293    (0x09B2, 0x09B2),
294    (0x09B6, 0x09B9),
295    (0x09BC, 0x09BC),
296    (0x09BE, 0x09C4),
297    (0x09C7, 0x09C8),
298    (0x09CB, 0x09CD),
299    (0x09D7, 0x09D7),
300    (0x09DC, 0x09DD),
301    (0x09DF, 0x09E3),
302    (0x09E6, 0x09F1),
303    (0x0A02, 0x0A02),
304    (0x0A05, 0x0A0A),
305    (0x0A0F, 0x0A10),
306    (0x0A13, 0x0A28),
307    (0x0A2A, 0x0A30),
308    (0x0A32, 0x0A33),
309    (0x0A35, 0x0A36),
310    (0x0A38, 0x0A39),
311    (0x0A3C, 0x0A3C),
312    (0x0A3E, 0x0A42),
313    (0x0A47, 0x0A48),
314    (0x0A4B, 0x0A4D),
315    (0x0A59, 0x0A5C),
316    (0x0A5E, 0x0A5E),
317    (0x0A66, 0x0A74),
318    (0x0A81, 0x0A83),
319    (0x0A85, 0x0A8B),
320    (0x0A8D, 0x0A8D),
321    (0x0A8F, 0x0A91),
322    (0x0A93, 0x0AA8),
323    (0x0AAA, 0x0AB0),
324    (0x0AB2, 0x0AB3),
325    (0x0AB5, 0x0AB9),
326    (0x0ABC, 0x0AC5),
327    (0x0AC7, 0x0AC9),
328    (0x0ACB, 0x0ACD),
329    (0x0AE0, 0x0AE0),
330    (0x0AE6, 0x0AEF),
331    (0x0B01, 0x0B03),
332    (0x0B05, 0x0B0C),
333    (0x0B0F, 0x0B10),
334    (0x0B13, 0x0B28),
335    (0x0B2A, 0x0B30),
336    (0x0B32, 0x0B33),
337    (0x0B36, 0x0B39),
338    (0x0B3C, 0x0B43),
339    (0x0B47, 0x0B48),
340    (0x0B4B, 0x0B4D),
341    (0x0B56, 0x0B57),
342    (0x0B5C, 0x0B5D),
343    (0x0B5F, 0x0B61),
344    (0x0B66, 0x0B6F),
345    (0x0B82, 0x0B83),
346    (0x0B85, 0x0B8A),
347    (0x0B8E, 0x0B90),
348    (0x0B92, 0x0B95),
349    (0x0B99, 0x0B9A),
350    (0x0B9C, 0x0B9C),
351    (0x0B9E, 0x0B9F),
352    (0x0BA3, 0x0BA4),
353    (0x0BA8, 0x0BAA),
354    (0x0BAE, 0x0BB5),
355    (0x0BB7, 0x0BB9),
356    (0x0BBE, 0x0BC2),
357    (0x0BC6, 0x0BC8),
358    (0x0BCA, 0x0BCD),
359    (0x0BD7, 0x0BD7),
360    (0x0BE7, 0x0BEF),
361    (0x0C01, 0x0C03),
362    (0x0C05, 0x0C0C),
363    (0x0C0E, 0x0C10),
364    (0x0C12, 0x0C28),
365    (0x0C2A, 0x0C33),
366    (0x0C35, 0x0C39),
367    (0x0C3E, 0x0C44),
368    (0x0C46, 0x0C48),
369    (0x0C4A, 0x0C4D),
370    (0x0C55, 0x0C56),
371    (0x0C60, 0x0C61),
372    (0x0C66, 0x0C6F),
373    (0x0C82, 0x0C83),
374    (0x0C85, 0x0C8C),
375    (0x0C8E, 0x0C90),
376    (0x0C92, 0x0CA8),
377    (0x0CAA, 0x0CB3),
378    (0x0CB5, 0x0CB9),
379    (0x0CBE, 0x0CC4),
380    (0x0CC6, 0x0CC8),
381    (0x0CCA, 0x0CCD),
382    (0x0CD5, 0x0CD6),
383    (0x0CDE, 0x0CDE),
384    (0x0CE0, 0x0CE1),
385    (0x0CE6, 0x0CEF),
386    (0x0D02, 0x0D03),
387    (0x0D05, 0x0D0C),
388    (0x0D0E, 0x0D10),
389    (0x0D12, 0x0D28),
390    (0x0D2A, 0x0D39),
391    (0x0D3E, 0x0D43),
392    (0x0D46, 0x0D48),
393    (0x0D4A, 0x0D4D),
394    (0x0D57, 0x0D57),
395    (0x0D60, 0x0D61),
396    (0x0D66, 0x0D6F),
397    (0x0E01, 0x0E2E),
398    (0x0E30, 0x0E3A),
399    (0x0E40, 0x0E4E),
400    (0x0E50, 0x0E59),
401    (0x0E81, 0x0E82),
402    (0x0E84, 0x0E84),
403    (0x0E87, 0x0E88),
404    (0x0E8A, 0x0E8A),
405    (0x0E8D, 0x0E8D),
406    (0x0E94, 0x0E97),
407    (0x0E99, 0x0E9F),
408    (0x0EA1, 0x0EA3),
409    (0x0EA5, 0x0EA5),
410    (0x0EA7, 0x0EA7),
411    (0x0EAA, 0x0EAB),
412    (0x0EAD, 0x0EAE),
413    (0x0EB0, 0x0EB9),
414    (0x0EBB, 0x0EBD),
415    (0x0EC0, 0x0EC4),
416    (0x0EC6, 0x0EC6),
417    (0x0EC8, 0x0ECD),
418    (0x0ED0, 0x0ED9),
419    (0x0F18, 0x0F19),
420    (0x0F20, 0x0F29),
421    (0x0F35, 0x0F35),
422    (0x0F37, 0x0F37),
423    (0x0F39, 0x0F39),
424    (0x0F3E, 0x0F47),
425    (0x0F49, 0x0F69),
426    (0x0F71, 0x0F84),
427    (0x0F86, 0x0F8B),
428    (0x0F90, 0x0F95),
429    (0x0F97, 0x0F97),
430    (0x0F99, 0x0FAD),
431    (0x0FB1, 0x0FB7),
432    (0x0FB9, 0x0FB9),
433    (0x10A0, 0x10C5),
434    (0x10D0, 0x10F6),
435    (0x1100, 0x1100),
436    (0x1102, 0x1103),
437    (0x1105, 0x1107),
438    (0x1109, 0x1109),
439    (0x110B, 0x110C),
440    (0x110E, 0x1112),
441    (0x113C, 0x113C),
442    (0x113E, 0x113E),
443    (0x1140, 0x1140),
444    (0x114C, 0x114C),
445    (0x114E, 0x114E),
446    (0x1150, 0x1150),
447    (0x1154, 0x1155),
448    (0x1159, 0x1159),
449    (0x115F, 0x1161),
450    (0x1163, 0x1163),
451    (0x1165, 0x1165),
452    (0x1167, 0x1167),
453    (0x1169, 0x1169),
454    (0x116D, 0x116E),
455    (0x1172, 0x1173),
456    (0x1175, 0x1175),
457    (0x119E, 0x119E),
458    (0x11A8, 0x11A8),
459    (0x11AB, 0x11AB),
460    (0x11AE, 0x11AF),
461    (0x11B7, 0x11B8),
462    (0x11BA, 0x11BA),
463    (0x11BC, 0x11C2),
464    (0x11EB, 0x11EB),
465    (0x11F0, 0x11F0),
466    (0x11F9, 0x11F9),
467    (0x1E00, 0x1E9B),
468    (0x1EA0, 0x1EF9),
469    (0x1F00, 0x1F15),
470    (0x1F18, 0x1F1D),
471    (0x1F20, 0x1F45),
472    (0x1F48, 0x1F4D),
473    (0x1F50, 0x1F57),
474    (0x1F59, 0x1F59),
475    (0x1F5B, 0x1F5B),
476    (0x1F5D, 0x1F5D),
477    (0x1F5F, 0x1F7D),
478    (0x1F80, 0x1FB4),
479    (0x1FB6, 0x1FBC),
480    (0x1FBE, 0x1FBE),
481    (0x1FC2, 0x1FC4),
482    (0x1FC6, 0x1FCC),
483    (0x1FD0, 0x1FD3),
484    (0x1FD6, 0x1FDB),
485    (0x1FE0, 0x1FEC),
486    (0x1FF2, 0x1FF4),
487    (0x1FF6, 0x1FFC),
488    (0x20D0, 0x20DC),
489    (0x20E1, 0x20E1),
490    (0x2126, 0x2126),
491    (0x212A, 0x212B),
492    (0x212E, 0x212E),
493    (0x2180, 0x2182),
494    (0x3005, 0x3005),
495    (0x3007, 0x3007),
496    (0x3021, 0x302F),
497    (0x3031, 0x3035),
498    (0x3041, 0x3094),
499    (0x3099, 0x309A),
500    (0x309D, 0x309E),
501    (0x30A1, 0x30FA),
502    (0x30FC, 0x30FE),
503    (0x3105, 0x312C),
504    (0x4E00, 0x9FA5),
505    (0xAC00, 0xD7A3),
506];
507
508/// True when the code point is in one of the ranges of the table.
509fn in_table(table: &[(u32, u32)], c: u32) -> bool {
510    table
511        .binary_search_by(|&(lo, hi)| {
512            if hi < c {
513                std::cmp::Ordering::Less
514            } else if lo > c {
515                std::cmp::Ordering::Greater
516            } else {
517                std::cmp::Ordering::Equal
518            }
519        })
520        .is_ok()
521}
522
523/// `map_sql_identifier_to_xml_name`: the XML name for a SQL name, with `_xHHHH_` for each character that the name cannot have. `fully_escaped` also escapes each `:` and a name that starts with `xml`, and `escape_period` escapes each `.`.
524#[must_use]
525pub fn xml_name(ident: &str, fully_escaped: bool, escape_period: bool) -> String {
526    let mut out = String::with_capacity(ident.len());
527    let mut chars = ident.char_indices().peekable();
528    while let Some((i, c)) = chars.next() {
529        let first = i == 0;
530        let next = chars.peek().map(|&(_, n)| n);
531        if c == ':' && (first || fully_escaped) {
532            out.push_str("_x003A_");
533        } else if c == '_' && next == Some('x') {
534            out.push_str("_x005F_");
535        } else if fully_escaped
536            && first
537            && ident.get(..3).is_some_and(|p| p.eq_ignore_ascii_case("xml"))
538        {
539            out.push_str(if c == 'x' { "_x0078_" } else { "_x0058_" });
540        } else if escape_period && c == '.' {
541            out.push_str("_x002E_");
542        } else {
543            let table = if first { NAME_FIRST } else { NAME_CHAR };
544            if in_table(table, u32::from(c)) {
545                out.push(c);
546            } else {
547                out.push_str(&format!("_x{:04X}_", u32::from(c)));
548            }
549        }
550    }
551    out
552}
553
554/// `map_xml_name_to_sql_identifier`: the SQL name for an XML name, with the character for each `_xHHHH_`.
555#[must_use]
556pub fn sql_name(name: &str) -> String {
557    let bytes = name.as_bytes();
558    let mut out = String::with_capacity(name.len());
559    let mut i = 0;
560    while i < bytes.len() {
561        let escape = bytes.get(i..i + 7).filter(|w| {
562            w[0] == b'_'
563                && w[1] == b'x'
564                && w[2..6].iter().all(u8::is_ascii_hexdigit)
565                && w[6] == b'_'
566        });
567        if let Some(w) = escape {
568            let hex = std::str::from_utf8(&w[2..6]).unwrap_or("0");
569            let code = u32::from_str_radix(hex, 16).unwrap_or(0);
570            out.push(char::from_u32(code).unwrap_or(char::REPLACEMENT_CHARACTER));
571            i += 7;
572        } else {
573            let len = name[i..].chars().next().map_or(1, char::len_utf8);
574            out.push_str(&name[i..i + len]);
575            i += len;
576        }
577    }
578    out
579}
580
581/// `escape_xml`: the text with `&`, `<`, `>` and carriage return as references.
582#[must_use]
583pub fn escape_xml(text: &str) -> String {
584    let mut out = String::with_capacity(text.len());
585    for c in text.chars() {
586        match c {
587            '&' => out.push_str("&amp;"),
588            '<' => out.push_str("&lt;"),
589            '>' => out.push_str("&gt;"),
590            '\r' => out.push_str("&#x0d;"),
591            _ => out.push(c),
592        }
593    }
594    out
595}
596
597/// `xmlEncodeSpecialChars` of libxml2, for `xmltext`: the text with `&`, `<`, `>`, `"` and carriage return as references.
598#[must_use]
599pub fn xml_text(text: &str) -> String {
600    let mut out = String::with_capacity(text.len());
601    for c in text.chars() {
602        match c {
603            '&' => out.push_str("&amp;"),
604            '<' => out.push_str("&lt;"),
605            '>' => out.push_str("&gt;"),
606            '"' => out.push_str("&quot;"),
607            '\r' => out.push_str("&#13;"),
608            _ => out.push(c),
609        }
610    }
611    out
612}
613
614/// The value of an attribute as the text writer of libxml2 writes it with no document: the markup characters, the white space other than the space and each character that is not ASCII as references. It drops the other control characters.
615fn attribute_value(text: &str, out: &mut String) {
616    for c in text.chars() {
617        match c {
618            '\n' => out.push_str("&#10;"),
619            '\r' => out.push_str("&#13;"),
620            '\t' => out.push_str("&#9;"),
621            '"' => out.push_str("&quot;"),
622            '<' => out.push_str("&lt;"),
623            '>' => out.push_str("&gt;"),
624            '&' => out.push_str("&amp;"),
625            c if u32::from(c) < 0x20 => {}
626            c if c.is_ascii() => out.push(c),
627            c => out.push_str(&format!("&#x{:X};", u32::from(c))),
628        }
629    }
630}
631
632/// `xmlelement`: the element with the attributes that have a value, and the content as it is. An element with no content is an empty element tag.
633#[must_use]
634pub fn xml_element(name: &str, attributes: &[(&str, &str)], content: &[String]) -> String {
635    let mut out = String::new();
636    out.push('<');
637    out.push_str(name);
638    for (attr, value) in attributes {
639        out.push(' ');
640        out.push_str(attr);
641        out.push_str("=\"");
642        attribute_value(value, &mut out);
643        out.push('"');
644    }
645    if content.is_empty() {
646        out.push_str("/>");
647    } else {
648        out.push('>');
649        for part in content {
650            out.push_str(part);
651        }
652        out.push_str("</");
653        out.push_str(name);
654        out.push('>');
655    }
656    out
657}
658
659/// `xmlconcat`: the values after their XML declarations, with one declaration for all. The declaration has the version when all the values have the same version, and the standalone part when all the values have it.
660#[must_use]
661pub fn xml_concat(values: &[&str]) -> String {
662    let mut standalone_all = 1;
663    let mut version_all: Option<&[u8]> = None;
664    let mut no_version = false;
665    let mut body = Vec::new();
666    for value in values {
667        let bytes = value.as_bytes();
668        let (len, version, standalone) = match parse_xml_decl(bytes) {
669            Ok(decl) => (decl.len, decl.version, decl.standalone),
670            Err(_) => (0, None, -1),
671        };
672        if standalone == 0 && standalone_all == 1 {
673            standalone_all = 0;
674        }
675        if standalone < 0 {
676            standalone_all = -1;
677        }
678        match (version, version_all) {
679            (None, _) => no_version = true,
680            (Some(v), None) => version_all = Some(v),
681            (Some(v), Some(all)) if v != all => no_version = true,
682            _ => {}
683        }
684        body.extend_from_slice(&bytes[len..]);
685    }
686    let mut out = Vec::new();
687    if !no_version || standalone_all >= 0 {
688        print_xml_decl(&mut out, if no_version { None } else { version_all }, standalone_all);
689    }
690    out.extend_from_slice(&body);
691    String::from_utf8(out).unwrap_or_default()
692}
693
694/// The `STANDALONE` clause of `XMLROOT`, as `XmlStandaloneType`.
695#[derive(Clone, Copy, Debug, PartialEq, Eq)]
696pub enum XmlStandalone {
697    Yes,
698    No,
699    NoValue,
700    /// The query does not give the clause, and the value keeps its own.
701    Omitted,
702}
703
704/// `xmlroot`: the value with a new XML declaration. A version of `None` is `NO VALUE`.
705#[must_use]
706pub fn xml_root(data: &str, version: Option<&str>, standalone: XmlStandalone) -> String {
707    let bytes = data.as_bytes();
708    let (len, old) = match parse_xml_decl(bytes) {
709        Ok(decl) => (decl.len, decl.standalone),
710        Err(_) => (0, -1),
711    };
712    let standalone = match standalone {
713        XmlStandalone::Yes => 1,
714        XmlStandalone::No => 0,
715        XmlStandalone::NoValue => -1,
716        XmlStandalone::Omitted => old,
717    };
718    let mut out = Vec::new();
719    print_xml_decl(&mut out, version.map(str::as_bytes), standalone);
720    out.extend_from_slice(&bytes[len..]);
721    String::from_utf8(out).unwrap_or_default()
722}
723
724/// The error `2200T` of `xmlpi`.
725fn invalid_pi(detail: String) -> TypeError {
726    let mut error = TypeError::new(
727        SqlState::INVALID_XML_PROCESSING_INSTRUCTION,
728        "invalid XML processing instruction".to_string(),
729    );
730    error.detail = Some(detail);
731    error
732}
733
734/// `xmlpi`: the processing instruction, or `None` when the argument is null. The check of the target comes before the check for null, as the standard says.
735///
736/// # Errors
737///
738/// `2200T` for the target `xml` in any case and for an argument with `?>`.
739pub fn xml_pi(target: &str, arg: Option<&str>, null: bool) -> Result<Option<String>, TypeError> {
740    if target.eq_ignore_ascii_case("xml") {
741        return Err(invalid_pi(format!(
742            "XML processing instruction target name cannot be \"{target}\"."
743        )));
744    }
745    if null {
746        return Ok(None);
747    }
748    let mut out = format!("<?{target}");
749    if let Some(arg) = arg {
750        if arg.contains("?>") {
751            return Err(invalid_pi(
752                "XML processing instruction cannot contain \"?>\".".to_string(),
753            ));
754        }
755        out.push(' ');
756        out.push_str(arg.trim_start_matches(' '));
757    }
758    out.push_str("?>");
759    Ok(Some(out))
760}
761
762/// `xmlcomment`: the comment with the text.
763///
764/// # Errors
765///
766/// `2200S` for a text with `--` or with `-` at the end.
767pub fn xml_comment(text: &str) -> Result<String, TypeError> {
768    if text.contains("--") || text.ends_with('-') {
769        return Err(TypeError::new(
770            SqlState::INVALID_XML_COMMENT,
771            "invalid XML comment".to_string(),
772        ));
773    }
774    Ok(format!("<!--{text}-->"))
775}
776
777/// `xmlTextWriterWriteBase64` of libxml2: base64 with a line break after each 72 characters.
778#[must_use]
779pub fn xml_base64(bytes: &[u8]) -> String {
780    const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
781    let mut out = String::with_capacity(bytes.len() * 4 / 3 + 4);
782    let mut line = 0;
783    for chunk in bytes.chunks(3) {
784        if line >= 72 {
785            out.push_str("\r\n");
786            line = 0;
787        }
788        let b = [chunk[0], chunk.get(1).copied().unwrap_or(0), chunk.get(2).copied().unwrap_or(0)];
789        let n = u32::from(b[0]) << 16 | u32::from(b[1]) << 8 | u32::from(b[2]);
790        for k in 0..4 {
791            if k <= chunk.len() {
792                out.push(char::from(ALPHABET[((n >> (18 - 6 * k)) & 0x3f) as usize]));
793            } else {
794                out.push('=');
795            }
796        }
797        line += 4;
798    }
799    out
800}
801
802/// `xmlTextWriterWriteBinHex` of libxml2: two hexadecimal digits in upper case for each byte.
803#[must_use]
804pub fn xml_binhex(bytes: &[u8]) -> String {
805    let mut out = String::with_capacity(bytes.len() * 2);
806    for b in bytes {
807        out.push_str(&format!("{b:02X}"));
808    }
809    out
810}
811
812#[cfg(test)]
813mod tests {
814    use super::*;
815
816    #[test]
817    fn names() {
818        assert_eq!(xml_name("a b", false, false), "a_x0020_b");
819        assert_eq!(xml_name(":x", false, false), "_x003A_x");
820        assert_eq!(xml_name("c:d", false, false), "c:d");
821        assert_eq!(xml_name("c:d", true, false), "c_x003A_d");
822        assert_eq!(xml_name("_x", false, false), "_x005F_x");
823        assert_eq!(xml_name("1a", false, false), "_x0031_a");
824        assert_eq!(xml_name("xmlfoo", false, false), "xmlfoo");
825        assert_eq!(xml_name("XmLfoo", true, false), "_x0058_mLfoo");
826        assert_eq!(xml_name("a.b", true, true), "a_x002E_b");
827        assert_eq!(xml_name("\u{e9}", false, false), "\u{e9}");
828        assert_eq!(xml_name("\u{101d0}", false, false), "_x101D0_");
829        assert_eq!(sql_name("a_x0020_b"), "a b");
830        assert_eq!(sql_name("_x005F_x"), "_x");
831        assert_eq!(sql_name("_x00"), "_x00");
832    }
833
834    #[test]
835    fn text() {
836        assert_eq!(escape_xml("<&>\r\"'"), "&lt;&amp;&gt;&#x0d;\"'");
837        assert_eq!(xml_text("<a & b>\r\""), "&lt;a &amp; b&gt;&#13;&quot;");
838        assert_eq!(xml_element("a", &[], &[]), "<a/>");
839        assert_eq!(xml_element("a", &[], &[String::new()]), "<a></a>");
840        assert_eq!(
841            xml_element("a", &[("b", "<\"\r\n\t'\u{e9}\u{1}")], &["x".to_string()]),
842            "<a b=\"&lt;&quot;&#13;&#10;&#9;'&#xE9;\">x</a>"
843        );
844    }
845
846    #[test]
847    fn declarations() {
848        assert_eq!(xml_concat(&["<a/>", "<b/>"]), "<a/><b/>");
849        assert_eq!(
850            xml_concat(&[
851                "<?xml version=\"1.1\"?><a/>",
852                "<?xml version=\"1.1\" standalone=\"no\"?><b/>"
853            ]),
854            "<?xml version=\"1.1\"?><a/><b/>"
855        );
856        assert_eq!(
857            xml_concat(&["<?xml version=\"1.0\" standalone=\"yes\"?><a/>", "<b/>"]),
858            "<a/><b/>"
859        );
860        assert_eq!(
861            xml_root("<a/>", Some("1.1"), XmlStandalone::Omitted),
862            "<?xml version=\"1.1\"?><a/>"
863        );
864        assert_eq!(
865            xml_root(
866                "<?xml version=\"1.1\" standalone=\"yes\"?><a/>",
867                None,
868                XmlStandalone::Omitted
869            ),
870            "<?xml version=\"1.0\" standalone=\"yes\"?><a/>"
871        );
872        assert_eq!(xml_root("<a/>", Some("1.0"), XmlStandalone::NoValue), "<a/>");
873    }
874
875    #[test]
876    fn others() {
877        assert_eq!(xml_pi("foo", Some("   bar"), false).unwrap().as_deref(), Some("<?foo bar?>"));
878        assert_eq!(xml_pi("foo", None, true).unwrap(), None);
879        assert!(xml_pi("XmL", None, true).is_err());
880        assert!(xml_pi("foo", Some("a?>"), false).is_err());
881        assert_eq!(xml_comment("a-b").unwrap(), "<!--a-b-->");
882        assert!(xml_comment("a--b").is_err());
883        assert!(xml_comment("a-").is_err());
884        assert_eq!(xml_base64(&[0xab]), "qw==");
885        assert_eq!(xml_base64(&[0xab, 0xcd]), "q80=");
886        assert_eq!(xml_base64(&[0xab; 54]).len(), 72);
887        assert_eq!(&xml_base64(&[0xab; 55])[72..], "\r\nqw==");
888        assert_eq!(xml_binhex(&[1, 2, 0xff]), "0102FF");
889    }
890}