• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

javadev / underscore-java / #4211

04 Jun 2025 06:13PM CUT coverage: 100.0%. Remained the same
#4211

push

web-flow
Spring boot 3.5.0

4519 of 4519 relevant lines covered (100.0%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/main/java/com/github/underscore/Xml.java
1
/*
2
 * The MIT License (MIT)
3
 *
4
 * Copyright 2015-2025 Valentyn Kolesnikov
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
package com.github.underscore;
25

26
import static java.nio.charset.StandardCharsets.UTF_8;
27

28
import java.util.ArrayList;
29
import java.util.Collection;
30
import java.util.Collections;
31
import java.util.HashMap;
32
import java.util.Iterator;
33
import java.util.LinkedHashMap;
34
import java.util.LinkedHashSet;
35
import java.util.List;
36
import java.util.Map;
37
import java.util.Set;
38
import java.util.function.BiFunction;
39
import java.util.function.Function;
40

41
@SuppressWarnings({
42
    "java:S107",
43
    "java:S1119",
44
    "java:S2583",
45
    "java:S3740",
46
    "java:S3776",
47
    "java:S4276"
48
})
49
public final class Xml {
50
    private Xml() {}
51

52
    private static final String NULL = "null";
53
    private static final String ELEMENT_TEXT = "element";
54
    private static final String CDATA = "#cdata-section";
55
    private static final String COMMENT = "#comment";
56
    private static final String ENCODING = "#encoding";
57
    private static final String STANDALONE = "#standalone";
58
    private static final String OMITXMLDECLARATION = "#omit-xml-declaration";
59
    private static final String YES = "yes";
60
    private static final String TEXT = "#text";
61
    private static final String NUMBER = "-number";
62
    private static final String ELEMENT = "<" + ELEMENT_TEXT + ">";
63
    private static final String CLOSED_ELEMENT = "</" + ELEMENT_TEXT + ">";
64
    private static final String EMPTY_ELEMENT = ELEMENT + CLOSED_ELEMENT;
65
    private static final String NULL_TRUE = " " + NULL + "=\"true\"/>";
66
    private static final String NUMBER_TEXT = " number=\"true\"";
67
    private static final String NUMBER_TRUE = NUMBER_TEXT + ">";
68
    private static final String ARRAY = "-array";
69
    private static final String ARRAY_TRUE = " array=\"true\"";
70
    private static final String NULL_ELEMENT = "<" + ELEMENT_TEXT + NULL_TRUE;
71
    private static final String BOOLEAN = "-boolean";
72
    private static final String TRUE = "true";
73
    private static final String SELF_CLOSING = "-self-closing";
74
    private static final String STRING = "-string";
75
    private static final String NULL_ATTR = "-null";
76
    private static final String EMPTY_ARRAY = "-empty-array";
77
    private static final String QUOT = "&quot;";
78
    private static final String XML_HEADER = "<?xml ";
79
    private static final String DOCTYPE_TEXT = "!DOCTYPE";
80
    private static final String ROOT = "root";
81
    private static final String DOCTYPE_HEADER = "<" + DOCTYPE_TEXT + " ";
82
    private static final Set<Character> SKIPPED_CHARS = Set.of(' ', '\n', '\r');
1✔
83
    private static final Map<String, String> XML_UNESCAPE = new HashMap<>();
1✔
84
    private static final org.w3c.dom.Document DOCUMENT = Document.createDocument();
1✔
85

86
    static {
87
        XML_UNESCAPE.put(QUOT, "\"");
1✔
88
        XML_UNESCAPE.put("&amp;", "&");
1✔
89
        XML_UNESCAPE.put("&lt;", "<");
1✔
90
        XML_UNESCAPE.put("&gt;", ">");
1✔
91
        XML_UNESCAPE.put("&apos;", "'");
1✔
92
    }
1✔
93

94
    public enum ArrayTrue {
1✔
95
        ADD,
1✔
96
        SKIP
1✔
97
    }
98

99
    public static class XmlStringBuilder {
100
        public enum Step {
1✔
101
            TWO_SPACES(2),
1✔
102
            THREE_SPACES(3),
1✔
103
            FOUR_SPACES(4),
1✔
104
            COMPACT(0),
1✔
105
            TABS(1);
1✔
106
            private final int ident;
107

108
            Step(int ident) {
1✔
109
                this.ident = ident;
1✔
110
            }
1✔
111

112
            public int getIdent() {
113
                return ident;
1✔
114
            }
115
        }
116

117
        protected final StringBuilder builder;
118
        private final Step identStep;
119
        private int ident;
120

121
        public XmlStringBuilder() {
1✔
122
            builder = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n");
1✔
123
            identStep = Step.TWO_SPACES;
1✔
124
            ident = 2;
1✔
125
        }
1✔
126

127
        public XmlStringBuilder(StringBuilder builder, Step identStep, int ident) {
1✔
128
            this.builder = builder;
1✔
129
            this.identStep = identStep;
1✔
130
            this.ident = ident;
1✔
131
        }
1✔
132

133
        public XmlStringBuilder append(final String string) {
134
            builder.append(string);
1✔
135
            return this;
1✔
136
        }
137

138
        public XmlStringBuilder fillSpaces() {
139
            builder.append(
1✔
140
                    String.valueOf(identStep == Step.TABS ? '\t' : ' ').repeat(Math.max(0, ident)));
1✔
141
            return this;
1✔
142
        }
143

144
        public XmlStringBuilder incIdent() {
145
            ident += identStep.getIdent();
1✔
146
            return this;
1✔
147
        }
148

149
        public XmlStringBuilder decIdent() {
150
            ident -= identStep.getIdent();
1✔
151
            return this;
1✔
152
        }
153

154
        public XmlStringBuilder newLine() {
155
            if (identStep != Step.COMPACT) {
1✔
156
                builder.append("\n");
1✔
157
            }
158
            return this;
1✔
159
        }
160

161
        public int getIdent() {
162
            return ident;
1✔
163
        }
164

165
        public Step getIdentStep() {
166
            return identStep;
1✔
167
        }
168

169
        public String toString() {
170
            return builder.toString() + "\n</root>";
1✔
171
        }
172
    }
173

174
    public static class XmlStringBuilderWithoutRoot extends XmlStringBuilder {
175
        public XmlStringBuilderWithoutRoot(
176
                XmlStringBuilder.Step identStep, String encoding, String standalone) {
177
            super(
1✔
178
                    new StringBuilder(
179
                            "<?xml version=\"1.0\" encoding=\""
180
                                    + XmlValue.escape(encoding).replace("\"", QUOT)
1✔
181
                                    + "\""
182
                                    + standalone
183
                                    + "?>"
184
                                    + (identStep == Step.COMPACT ? "" : "\n")),
1✔
185
                    identStep,
186
                    0);
187
        }
1✔
188

189
        @Override
190
        public String toString() {
191
            return builder.toString();
1✔
192
        }
193
    }
194

195
    public static class XmlStringBuilderWithoutHeader extends XmlStringBuilder {
196
        public XmlStringBuilderWithoutHeader(XmlStringBuilder.Step identStep, int ident) {
197
            super(new StringBuilder(), identStep, ident);
1✔
198
        }
1✔
199

200
        @Override
201
        public String toString() {
202
            return builder.toString();
1✔
203
        }
204
    }
205

206
    public static class XmlStringBuilderText extends XmlStringBuilderWithoutHeader {
207
        public XmlStringBuilderText(XmlStringBuilder.Step identStep, int ident) {
208
            super(identStep, ident);
1✔
209
        }
1✔
210
    }
211

212
    public static class XmlArray {
213
        private XmlArray() {}
214

215
        public static void writeXml(
216
                Collection<?> collection,
217
                String name,
218
                XmlStringBuilder builder,
219
                boolean parentTextFound,
220
                Set<String> namespaces,
221
                boolean addArray,
222
                String arrayTrue) {
223
            if (collection == null) {
1✔
224
                builder.append(NULL);
1✔
225
                return;
1✔
226
            }
227

228
            if (name != null) {
1✔
229
                builder.fillSpaces().append("<").append(XmlValue.escapeName(name, namespaces));
1✔
230
                if (addArray) {
1✔
231
                    builder.append(arrayTrue);
1✔
232
                }
233
                if (collection.isEmpty()) {
1✔
234
                    builder.append(" empty-array=\"true\"");
1✔
235
                }
236
                builder.append(">").incIdent();
1✔
237
                if (!collection.isEmpty()) {
1✔
238
                    builder.newLine();
1✔
239
                }
240
            }
241
            writeXml(collection, builder, name, parentTextFound, namespaces, arrayTrue);
1✔
242
            if (name != null) {
1✔
243
                builder.decIdent();
1✔
244
                if (!collection.isEmpty()) {
1✔
245
                    builder.newLine().fillSpaces();
1✔
246
                }
247
                builder.append("</").append(XmlValue.escapeName(name, namespaces)).append(">");
1✔
248
            }
249
        }
1✔
250

251
        private static void writeXml(
252
                Collection<?> collection,
253
                XmlStringBuilder builder,
254
                String name,
255
                final boolean parentTextFound,
256
                Set<String> namespaces,
257
                String arrayTrue) {
258
            boolean localParentTextFound = parentTextFound;
1✔
259
            final List<?> entries = new ArrayList<>(collection);
1✔
260
            for (int index = 0; index < entries.size(); index += 1) {
1✔
261
                final Object value = entries.get(index);
1✔
262
                final boolean addNewLine =
1✔
263
                        index < entries.size() - 1
1✔
264
                                && !XmlValue.getMapKey(XmlValue.getMapValue(entries.get(index + 1)))
1✔
265
                                        .startsWith(TEXT);
1✔
266
                if (value == null) {
1✔
267
                    builder.fillSpaces()
1✔
268
                            .append(
1✔
269
                                    "<"
270
                                            + (name == null
1✔
271
                                                    ? ELEMENT_TEXT
1✔
272
                                                    : XmlValue.escapeName(name, namespaces))
1✔
273
                                            + (collection.size() == 1 ? arrayTrue : "")
1✔
274
                                            + NULL_TRUE);
275
                } else {
276
                    if (value instanceof Map
1✔
277
                            && ((Map) value).size() == 1
1✔
278
                            && XmlValue.getMapKey(value).equals("#item")
1✔
279
                            && XmlValue.getMapValue(value) instanceof Map) {
1✔
280
                        XmlObject.writeXml(
1✔
281
                                (Map) XmlValue.getMapValue(value),
1✔
282
                                null,
283
                                builder,
284
                                localParentTextFound,
285
                                namespaces,
286
                                true,
287
                                arrayTrue);
288
                        if (XmlValue.getMapKey(XmlValue.getMapValue(value)).startsWith(TEXT)) {
1✔
289
                            localParentTextFound = true;
1✔
290
                            continue;
1✔
291
                        }
292
                    } else {
293
                        XmlValue.writeXml(
1✔
294
                                value,
295
                                name == null ? ELEMENT_TEXT : name,
1✔
296
                                builder,
297
                                localParentTextFound,
298
                                namespaces,
299
                                collection.size() == 1 || value instanceof Collection,
1✔
300
                                arrayTrue);
301
                    }
302
                    localParentTextFound = false;
1✔
303
                }
304
                if (addNewLine) {
1✔
305
                    builder.newLine();
1✔
306
                }
307
            }
308
        }
1✔
309

310
        public static void writeXml(byte[] array, XmlStringBuilder builder) {
311
            if (array == null) {
1✔
312
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
313
            } else if (array.length == 0) {
1✔
314
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
315
            } else {
316
                for (int i = 0; i < array.length; i++) {
1✔
317
                    builder.fillSpaces().append(ELEMENT);
1✔
318
                    builder.append(String.valueOf(array[i]));
1✔
319
                    builder.append(CLOSED_ELEMENT);
1✔
320
                    if (i != array.length - 1) {
1✔
321
                        builder.newLine();
1✔
322
                    }
323
                }
324
            }
325
        }
1✔
326

327
        public static void writeXml(short[] array, XmlStringBuilder builder) {
328
            if (array == null) {
1✔
329
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
330
            } else if (array.length == 0) {
1✔
331
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
332
            } else {
333
                for (int i = 0; i < array.length; i++) {
1✔
334
                    builder.fillSpaces().append(ELEMENT);
1✔
335
                    builder.append(String.valueOf(array[i]));
1✔
336
                    builder.append(CLOSED_ELEMENT);
1✔
337
                    if (i != array.length - 1) {
1✔
338
                        builder.newLine();
1✔
339
                    }
340
                }
341
            }
342
        }
1✔
343

344
        public static void writeXml(int[] array, XmlStringBuilder builder) {
345
            if (array == null) {
1✔
346
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
347
            } else if (array.length == 0) {
1✔
348
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
349
            } else {
350
                for (int i = 0; i < array.length; i++) {
1✔
351
                    builder.fillSpaces().append(ELEMENT);
1✔
352
                    builder.append(String.valueOf(array[i]));
1✔
353
                    builder.append(CLOSED_ELEMENT);
1✔
354
                    if (i != array.length - 1) {
1✔
355
                        builder.newLine();
1✔
356
                    }
357
                }
358
            }
359
        }
1✔
360

361
        public static void writeXml(long[] array, XmlStringBuilder builder) {
362
            if (array == null) {
1✔
363
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
364
            } else if (array.length == 0) {
1✔
365
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
366
            } else {
367
                for (int i = 0; i < array.length; i++) {
1✔
368
                    builder.fillSpaces().append(ELEMENT);
1✔
369
                    builder.append(String.valueOf(array[i]));
1✔
370
                    builder.append(CLOSED_ELEMENT);
1✔
371
                    if (i != array.length - 1) {
1✔
372
                        builder.newLine();
1✔
373
                    }
374
                }
375
            }
376
        }
1✔
377

378
        public static void writeXml(float[] array, XmlStringBuilder builder) {
379
            if (array == null) {
1✔
380
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
381
            } else if (array.length == 0) {
1✔
382
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
383
            } else {
384
                for (int i = 0; i < array.length; i++) {
1✔
385
                    builder.fillSpaces().append(ELEMENT);
1✔
386
                    builder.append(String.valueOf(array[i]));
1✔
387
                    builder.append(CLOSED_ELEMENT);
1✔
388
                    if (i != array.length - 1) {
1✔
389
                        builder.newLine();
1✔
390
                    }
391
                }
392
            }
393
        }
1✔
394

395
        public static void writeXml(double[] array, XmlStringBuilder builder) {
396
            if (array == null) {
1✔
397
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
398
            } else if (array.length == 0) {
1✔
399
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
400
            } else {
401
                for (int i = 0; i < array.length; i++) {
1✔
402
                    builder.fillSpaces().append(ELEMENT);
1✔
403
                    builder.append(String.valueOf(array[i]));
1✔
404
                    builder.append(CLOSED_ELEMENT);
1✔
405
                    if (i != array.length - 1) {
1✔
406
                        builder.newLine();
1✔
407
                    }
408
                }
409
            }
410
        }
1✔
411

412
        public static void writeXml(boolean[] array, XmlStringBuilder builder) {
413
            if (array == null) {
1✔
414
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
415
            } else if (array.length == 0) {
1✔
416
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
417
            } else {
418
                for (int i = 0; i < array.length; i++) {
1✔
419
                    builder.fillSpaces().append(ELEMENT);
1✔
420
                    builder.append(String.valueOf(array[i]));
1✔
421
                    builder.append(CLOSED_ELEMENT);
1✔
422
                    if (i != array.length - 1) {
1✔
423
                        builder.newLine();
1✔
424
                    }
425
                }
426
            }
427
        }
1✔
428

429
        public static void writeXml(char[] array, XmlStringBuilder builder) {
430
            if (array == null) {
1✔
431
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
432
            } else if (array.length == 0) {
1✔
433
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
434
            } else {
435
                for (int i = 0; i < array.length; i++) {
1✔
436
                    builder.fillSpaces().append(ELEMENT);
1✔
437
                    builder.append(String.valueOf(array[i]));
1✔
438
                    builder.append(CLOSED_ELEMENT);
1✔
439
                    if (i != array.length - 1) {
1✔
440
                        builder.newLine();
1✔
441
                    }
442
                }
443
            }
444
        }
1✔
445

446
        public static void writeXml(
447
                Object[] array,
448
                String name,
449
                XmlStringBuilder builder,
450
                boolean parentTextFound,
451
                Set<String> namespaces,
452
                String arrayTrue) {
453
            if (array == null) {
1✔
454
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
455
            } else if (array.length == 0) {
1✔
456
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
457
            } else {
458
                for (int i = 0; i < array.length; i++) {
1✔
459
                    XmlValue.writeXml(
1✔
460
                            array[i],
461
                            name == null ? ELEMENT_TEXT : name,
1✔
462
                            builder,
463
                            parentTextFound,
464
                            namespaces,
465
                            false,
466
                            arrayTrue);
467
                    if (i != array.length - 1) {
1✔
468
                        builder.newLine();
1✔
469
                    }
470
                }
471
            }
472
        }
1✔
473
    }
474

475
    public static class XmlObject {
476
        private XmlObject() {}
477

478
        @SuppressWarnings("unchecked")
479
        public static void writeXml(
480
                final Map map,
481
                final String name,
482
                final XmlStringBuilder builder,
483
                final boolean parentTextFound,
484
                final Set<String> namespaces,
485
                final boolean addArray,
486
                final String arrayTrue) {
487
            if (map == null) {
1✔
488
                XmlValue.writeXml(NULL, name, builder, false, namespaces, addArray, arrayTrue);
1✔
489
                return;
1✔
490
            }
491

492
            final List<XmlStringBuilder> elems = new ArrayList<>();
1✔
493
            final List<String> attrs = new ArrayList<>();
1✔
494
            final XmlStringBuilder.Step identStep = builder.getIdentStep();
1✔
495
            final int ident =
1✔
496
                    builder.getIdent() + (name == null ? 0 : builder.getIdentStep().getIdent());
1✔
497
            final List<Map.Entry> entries = new ArrayList<>(map.entrySet());
1✔
498
            final Set<String> attrKeys = new LinkedHashSet<>();
1✔
499
            fillNamespacesAndAttrs(map, namespaces, attrKeys);
1✔
500
            for (int index = 0; index < entries.size(); index += 1) {
1✔
501
                final Map.Entry entry = entries.get(index);
1✔
502
                final boolean addNewLine =
1✔
503
                        index < entries.size() - 1
1✔
504
                                && !String.valueOf(entries.get(index + 1).getKey())
1✔
505
                                        .startsWith(TEXT);
1✔
506
                if (String.valueOf(entry.getKey()).startsWith("-")
1✔
507
                        && entry.getValue() instanceof String) {
1✔
508
                    attrs.add(
1✔
509
                            " "
510
                                    + XmlValue.escapeName(
1✔
511
                                            String.valueOf(entry.getKey()).substring(1), namespaces)
1✔
512
                                    + "=\""
513
                                    + XmlValue.escape(String.valueOf(entry.getValue()))
1✔
514
                                            .replace("\"", QUOT)
1✔
515
                                    + "\"");
516
                } else if (String.valueOf(entry.getKey()).startsWith(TEXT)) {
1✔
517
                    addText(entry, elems, identStep, ident, attrKeys, attrs);
1✔
518
                } else {
519
                    boolean localParentTextFound =
1✔
520
                            !elems.isEmpty()
1✔
521
                                            && elems.get(elems.size() - 1)
1✔
522
                                                    instanceof XmlStringBuilderText
523
                                    || parentTextFound;
524
                    processElements(
1✔
525
                            entry,
526
                            identStep,
527
                            ident,
528
                            addNewLine,
529
                            elems,
530
                            namespaces,
531
                            localParentTextFound,
532
                            arrayTrue);
533
                }
534
            }
535
            if (addArray && !attrKeys.contains(ARRAY)) {
1✔
536
                attrs.add(arrayTrue);
1✔
537
            }
538
            addToBuilder(name, parentTextFound, builder, namespaces, attrs, elems);
1✔
539
        }
1✔
540

541
        @SuppressWarnings("unchecked")
542
        private static void fillNamespacesAndAttrs(
543
                final Map map, final Set<String> namespaces, final Set<String> attrKeys) {
544
            for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
1✔
545
                if (String.valueOf(entry.getKey()).startsWith("-")
1✔
546
                        && !(entry.getValue() instanceof Map)
1✔
547
                        && !(entry.getValue() instanceof List)) {
1✔
548
                    if (String.valueOf(entry.getKey()).startsWith("-xmlns:")) {
1✔
549
                        namespaces.add(String.valueOf(entry.getKey()).substring(7));
1✔
550
                    }
551
                    attrKeys.add(String.valueOf(entry.getKey()));
1✔
552
                }
553
            }
1✔
554
        }
1✔
555

556
        private static void addToBuilder(
557
                final String name,
558
                final boolean parentTextFound,
559
                final XmlStringBuilder builder,
560
                final Set<String> namespaces,
561
                final List<String> attrs,
562
                final List<XmlStringBuilder> elems) {
563
            final boolean selfClosing = attrs.remove(" self-closing=\"true\"");
1✔
564
            addOpenElement(name, parentTextFound, builder, namespaces, selfClosing, attrs, elems);
1✔
565
            if (!selfClosing) {
1✔
566
                for (XmlStringBuilder localBuilder1 : elems) {
1✔
567
                    builder.append(localBuilder1.toString());
1✔
568
                }
1✔
569
            }
570
            if (name != null) {
1✔
571
                builder.decIdent();
1✔
572
                if (!elems.isEmpty()
1✔
573
                        && !(elems.get(elems.size() - 1) instanceof XmlStringBuilderText)) {
1✔
574
                    builder.newLine().fillSpaces();
1✔
575
                }
576
                if (!selfClosing) {
1✔
577
                    builder.append("</").append(XmlValue.escapeName(name, namespaces)).append(">");
1✔
578
                }
579
            }
580
        }
1✔
581

582
        private static void addOpenElement(
583
                final String name,
584
                final boolean parentTextFound,
585
                final XmlStringBuilder builder,
586
                final Set<String> namespaces,
587
                final boolean selfClosing,
588
                final List<String> attrs,
589
                final List<XmlStringBuilder> elems) {
590
            if (name != null) {
1✔
591
                if (!parentTextFound) {
1✔
592
                    builder.fillSpaces();
1✔
593
                }
594
                builder.append("<")
1✔
595
                        .append(XmlValue.escapeName(name, namespaces))
1✔
596
                        .append(String.join("", attrs));
1✔
597
                if (selfClosing) {
1✔
598
                    builder.append("/");
1✔
599
                }
600
                builder.append(">").incIdent();
1✔
601
                if (!elems.isEmpty() && !(elems.get(0) instanceof XmlStringBuilderText)) {
1✔
602
                    builder.newLine();
1✔
603
                }
604
            }
605
        }
1✔
606

607
        private static void processElements(
608
                final Map.Entry entry,
609
                final XmlStringBuilder.Step identStep,
610
                final int ident,
611
                final boolean addNewLine,
612
                final List<XmlStringBuilder> elems,
613
                final Set<String> namespaces,
614
                final boolean parentTextFound,
615
                final String arrayTrue) {
616
            if (String.valueOf(entry.getKey()).startsWith(COMMENT)) {
1✔
617
                addComment(entry, identStep, ident, parentTextFound, addNewLine, elems);
1✔
618
            } else if (String.valueOf(entry.getKey()).startsWith(CDATA)) {
1✔
619
                addCdata(entry, identStep, ident, addNewLine, elems);
1✔
620
            } else if (entry.getValue() instanceof List && !((List) entry.getValue()).isEmpty()) {
1✔
621
                addElements(identStep, ident, entry, namespaces, elems, addNewLine, arrayTrue);
1✔
622
            } else {
623
                addElement(identStep, ident, entry, namespaces, elems, addNewLine, arrayTrue);
1✔
624
            }
625
        }
1✔
626

627
        private static void addText(
628
                final Map.Entry entry,
629
                final List<XmlStringBuilder> elems,
630
                final XmlStringBuilder.Step identStep,
631
                final int ident,
632
                final Set<String> attrKeys,
633
                final List<String> attrs) {
634
            if (entry.getValue() instanceof List) {
1✔
635
                for (Object value : (List) entry.getValue()) {
1✔
636
                    elems.add(
1✔
637
                            new XmlStringBuilderText(identStep, ident)
638
                                    .append(XmlValue.escape(String.valueOf(value))));
1✔
639
                }
1✔
640
            } else {
641
                if (entry.getValue() instanceof Number && !attrKeys.contains(NUMBER)) {
1✔
642
                    attrs.add(NUMBER_TEXT);
1✔
643
                } else if (entry.getValue() instanceof Boolean && !attrKeys.contains(BOOLEAN)) {
1✔
644
                    attrs.add(" boolean=\"true\"");
1✔
645
                } else if (entry.getValue() == null && !attrKeys.contains(NULL_ATTR)) {
1✔
646
                    attrs.add(" null=\"true\"");
1✔
647
                    return;
1✔
648
                } else if ("".equals(entry.getValue()) && !attrKeys.contains(STRING)) {
1✔
649
                    attrs.add(" string=\"true\"");
1✔
650
                    return;
1✔
651
                }
652
                elems.add(
1✔
653
                        new XmlStringBuilderText(identStep, ident)
654
                                .append(XmlValue.escape(String.valueOf(entry.getValue()))));
1✔
655
            }
656
        }
1✔
657

658
        private static void addElements(
659
                final XmlStringBuilder.Step identStep,
660
                final int ident,
661
                Map.Entry entry,
662
                Set<String> namespaces,
663
                final List<XmlStringBuilder> elems,
664
                final boolean addNewLine,
665
                final String arrayTrue) {
666
            boolean parentTextFound =
1✔
667
                    !elems.isEmpty() && elems.get(elems.size() - 1) instanceof XmlStringBuilderText;
1✔
668
            final XmlStringBuilder localBuilder =
1✔
669
                    new XmlStringBuilderWithoutHeader(identStep, ident);
670
            XmlArray.writeXml(
1✔
671
                    (List) entry.getValue(),
1✔
672
                    localBuilder,
673
                    String.valueOf(entry.getKey()),
1✔
674
                    parentTextFound,
675
                    namespaces,
676
                    arrayTrue);
677
            if (addNewLine) {
1✔
678
                localBuilder.newLine();
1✔
679
            }
680
            elems.add(localBuilder);
1✔
681
        }
1✔
682

683
        private static void addElement(
684
                final XmlStringBuilder.Step identStep,
685
                final int ident,
686
                Map.Entry entry,
687
                Set<String> namespaces,
688
                final List<XmlStringBuilder> elems,
689
                final boolean addNewLine,
690
                final String arrayTrue) {
691
            boolean parentTextFound =
1✔
692
                    !elems.isEmpty() && elems.get(elems.size() - 1) instanceof XmlStringBuilderText;
1✔
693
            XmlStringBuilder localBuilder = new XmlStringBuilderWithoutHeader(identStep, ident);
1✔
694
            XmlValue.writeXml(
1✔
695
                    entry.getValue(),
1✔
696
                    String.valueOf(entry.getKey()),
1✔
697
                    localBuilder,
698
                    parentTextFound,
699
                    namespaces,
700
                    false,
701
                    arrayTrue);
702
            if (addNewLine) {
1✔
703
                localBuilder.newLine();
1✔
704
            }
705
            elems.add(localBuilder);
1✔
706
        }
1✔
707

708
        private static void addComment(
709
                Map.Entry entry,
710
                XmlStringBuilder.Step identStep,
711
                int ident,
712
                boolean parentTextFound,
713
                boolean addNewLine,
714
                List<XmlStringBuilder> elems) {
715
            if (entry.getValue() instanceof List) {
1✔
716
                for (Iterator iterator = ((List) entry.getValue()).iterator();
1✔
717
                        iterator.hasNext(); ) {
1✔
718
                    elems.add(
1✔
719
                            addCommentValue(
1✔
720
                                    identStep,
721
                                    ident,
722
                                    String.valueOf(iterator.next()),
1✔
723
                                    parentTextFound,
724
                                    iterator.hasNext() || addNewLine));
1✔
725
                }
726
            } else {
727
                elems.add(
1✔
728
                        addCommentValue(
1✔
729
                                identStep,
730
                                ident,
731
                                String.valueOf(entry.getValue()),
1✔
732
                                parentTextFound,
733
                                addNewLine));
734
            }
735
        }
1✔
736

737
        private static XmlStringBuilder addCommentValue(
738
                XmlStringBuilder.Step identStep,
739
                int ident,
740
                String value,
741
                boolean parentTextFound,
742
                boolean addNewLine) {
743
            XmlStringBuilder localBuilder = new XmlStringBuilderWithoutHeader(identStep, ident);
1✔
744
            if (!parentTextFound) {
1✔
745
                localBuilder.fillSpaces();
1✔
746
            }
747
            localBuilder.append("<!--").append(value).append("-->");
1✔
748
            if (addNewLine) {
1✔
749
                localBuilder.newLine();
1✔
750
            }
751
            return localBuilder;
1✔
752
        }
753

754
        private static void addCdata(
755
                Map.Entry entry,
756
                XmlStringBuilder.Step identStep,
757
                int ident,
758
                boolean addNewLine,
759
                List<XmlStringBuilder> elems) {
760
            if (entry.getValue() instanceof List) {
1✔
761
                for (Iterator iterator = ((List) entry.getValue()).iterator();
1✔
762
                        iterator.hasNext(); ) {
1✔
763
                    elems.add(
1✔
764
                            addCdataValue(
1✔
765
                                    identStep,
766
                                    ident,
767
                                    String.valueOf(iterator.next()),
1✔
768
                                    iterator.hasNext() || addNewLine));
1✔
769
                }
770
            } else {
771
                elems.add(
1✔
772
                        addCdataValue(
1✔
773
                                identStep, ident, String.valueOf(entry.getValue()), addNewLine));
1✔
774
            }
775
        }
1✔
776

777
        private static XmlStringBuilder addCdataValue(
778
                XmlStringBuilder.Step identStep, int ident, String value, boolean addNewLine) {
779
            XmlStringBuilder localBuilder = new XmlStringBuilderText(identStep, ident);
1✔
780
            localBuilder.append("<![CDATA[").append(value).append("]]>");
1✔
781
            if (addNewLine) {
1✔
782
                localBuilder.newLine();
1✔
783
            }
784
            return localBuilder;
1✔
785
        }
786
    }
787

788
    public static class XmlValue {
789
        private XmlValue() {}
790

791
        public static void writeXml(
792
                Object value,
793
                String name,
794
                XmlStringBuilder builder,
795
                boolean parentTextFound,
796
                Set<String> namespaces,
797
                boolean addArray,
798
                String arrayTrue) {
799
            if (value instanceof Map) {
1✔
800
                XmlObject.writeXml(
1✔
801
                        (Map) value,
802
                        name,
803
                        builder,
804
                        parentTextFound,
805
                        namespaces,
806
                        addArray,
807
                        arrayTrue);
808
                return;
1✔
809
            }
810
            if (value instanceof Collection) {
1✔
811
                XmlArray.writeXml(
1✔
812
                        (Collection) value,
813
                        name,
814
                        builder,
815
                        parentTextFound,
816
                        namespaces,
817
                        addArray,
818
                        arrayTrue);
819
                return;
1✔
820
            }
821
            if (!parentTextFound) {
1✔
822
                builder.fillSpaces();
1✔
823
            }
824
            if (value == null) {
1✔
825
                builder.append("<" + XmlValue.escapeName(name, namespaces) + NULL_TRUE);
1✔
826
            } else if (value instanceof String) {
1✔
827
                if (((String) value).isEmpty()) {
1✔
828
                    builder.append(
1✔
829
                            "<"
830
                                    + XmlValue.escapeName(name, namespaces)
1✔
831
                                    + (addArray ? arrayTrue : ""));
1✔
832
                    if (name.startsWith("?")) {
1✔
833
                        builder.append("?>");
1✔
834
                    } else {
835
                        builder.append(" string=\"true\"/>");
1✔
836
                    }
837
                } else {
838
                    builder.append(
1✔
839
                            "<"
840
                                    + XmlValue.escapeName(name, namespaces)
1✔
841
                                    + (addArray ? arrayTrue : "")
1✔
842
                                    + (name.startsWith("?") ? " " : ">"));
1✔
843
                    builder.append(escape((String) value));
1✔
844
                    if (name.startsWith("?")) {
1✔
845
                        builder.append("?>");
1✔
846
                    } else {
847
                        builder.append("</" + XmlValue.escapeName(name, namespaces) + ">");
1✔
848
                    }
849
                }
850
            } else {
851
                processArrays(
1✔
852
                        value, builder, name, parentTextFound, namespaces, addArray, arrayTrue);
853
            }
854
        }
1✔
855

856
        private static void processArrays(
857
                Object value,
858
                XmlStringBuilder builder,
859
                String name,
860
                boolean parentTextFound,
861
                Set<String> namespaces,
862
                boolean addArray,
863
                String arrayTrue) {
864
            if (value instanceof Double) {
1✔
865
                if (((Double) value).isInfinite() || ((Double) value).isNaN()) {
1✔
866
                    builder.append(NULL_ELEMENT);
1✔
867
                } else {
868
                    builder.append(
1✔
869
                            "<"
870
                                    + XmlValue.escapeName(name, namespaces)
1✔
871
                                    + (addArray ? arrayTrue : "")
1✔
872
                                    + NUMBER_TRUE);
873
                    builder.append(value.toString());
1✔
874
                    builder.append("</" + XmlValue.escapeName(name, namespaces) + ">");
1✔
875
                }
876
            } else if (value instanceof Float) {
1✔
877
                if (((Float) value).isInfinite() || ((Float) value).isNaN()) {
1✔
878
                    builder.append(NULL_ELEMENT);
1✔
879
                } else {
880
                    builder.append("<" + XmlValue.escapeName(name, namespaces) + NUMBER_TRUE);
1✔
881
                    builder.append(value.toString());
1✔
882
                    builder.append("</" + XmlValue.escapeName(name, namespaces) + ">");
1✔
883
                }
884
            } else if (value instanceof Number) {
1✔
885
                builder.append(
1✔
886
                        "<"
887
                                + XmlValue.escapeName(name, namespaces)
1✔
888
                                + (addArray ? arrayTrue : "")
1✔
889
                                + NUMBER_TRUE);
890
                builder.append(value.toString());
1✔
891
                builder.append("</" + XmlValue.escapeName(name, namespaces) + ">");
1✔
892
            } else if (value instanceof Boolean) {
1✔
893
                builder.append(
1✔
894
                        "<"
895
                                + XmlValue.escapeName(name, namespaces)
1✔
896
                                + (addArray ? arrayTrue : "")
1✔
897
                                + " boolean=\"true\">");
898
                builder.append(value.toString());
1✔
899
                builder.append("</" + XmlValue.escapeName(name, namespaces) + ">");
1✔
900
            } else {
901
                builder.append("<" + XmlValue.escapeName(name, namespaces) + ">");
1✔
902
                if (value instanceof byte[]) {
1✔
903
                    builder.newLine().incIdent();
1✔
904
                    XmlArray.writeXml((byte[]) value, builder);
1✔
905
                    builder.decIdent().newLine().fillSpaces();
1✔
906
                } else if (value instanceof short[]) {
1✔
907
                    builder.newLine().incIdent();
1✔
908
                    XmlArray.writeXml((short[]) value, builder);
1✔
909
                    builder.decIdent().newLine().fillSpaces();
1✔
910
                } else {
911
                    processArrays2(value, builder, name, parentTextFound, namespaces, arrayTrue);
1✔
912
                }
913
                builder.append("</" + XmlValue.escapeName(name, namespaces) + ">");
1✔
914
            }
915
        }
1✔
916

917
        private static void processArrays2(
918
                Object value,
919
                XmlStringBuilder builder,
920
                String name,
921
                boolean parentTextFound,
922
                Set<String> namespaces,
923
                String arrayTrue) {
924
            if (value instanceof int[]) {
1✔
925
                builder.newLine().incIdent();
1✔
926
                XmlArray.writeXml((int[]) value, builder);
1✔
927
                builder.decIdent().newLine().fillSpaces();
1✔
928
            } else if (value instanceof long[]) {
1✔
929
                builder.newLine().incIdent();
1✔
930
                XmlArray.writeXml((long[]) value, builder);
1✔
931
                builder.decIdent().newLine().fillSpaces();
1✔
932
            } else if (value instanceof float[]) {
1✔
933
                builder.newLine().incIdent();
1✔
934
                XmlArray.writeXml((float[]) value, builder);
1✔
935
                builder.decIdent().newLine().fillSpaces();
1✔
936
            } else if (value instanceof double[]) {
1✔
937
                builder.newLine().incIdent();
1✔
938
                XmlArray.writeXml((double[]) value, builder);
1✔
939
                builder.decIdent().newLine().fillSpaces();
1✔
940
            } else if (value instanceof boolean[]) {
1✔
941
                builder.newLine().incIdent();
1✔
942
                XmlArray.writeXml((boolean[]) value, builder);
1✔
943
                builder.decIdent().newLine().fillSpaces();
1✔
944
            } else if (value instanceof char[]) {
1✔
945
                builder.newLine().incIdent();
1✔
946
                XmlArray.writeXml((char[]) value, builder);
1✔
947
                builder.decIdent().newLine().fillSpaces();
1✔
948
            } else if (value instanceof Object[]) {
1✔
949
                builder.newLine().incIdent();
1✔
950
                XmlArray.writeXml(
1✔
951
                        (Object[]) value, name, builder, parentTextFound, namespaces, arrayTrue);
952
                builder.decIdent().newLine().fillSpaces();
1✔
953
            } else {
954
                builder.append(value.toString());
1✔
955
            }
956
        }
1✔
957

958
        public static String escapeName(String name, Set<String> namespaces) {
959
            final int length = name.length();
1✔
960
            if (length == 0) {
1✔
961
                return "__EE__EMPTY__EE__";
1✔
962
            }
963
            final StringBuilder result = new StringBuilder();
1✔
964
            char ch = name.charAt(0);
1✔
965
            if (ch != ':') {
1✔
966
                try {
967
                    if (ch != '?') {
1✔
968
                        DOCUMENT.createElement(String.valueOf(ch));
1✔
969
                    }
970
                    result.append(ch);
1✔
971
                } catch (Exception ex) {
1✔
972
                    result.append("__").append(Base32.encode(Character.toString(ch))).append("__");
1✔
973
                }
1✔
974
            } else {
975
                result.append("__").append(Base32.encode(Character.toString(ch))).append("__");
1✔
976
            }
977
            for (int i = 1; i < length; ++i) {
1✔
978
                ch = name.charAt(i);
1✔
979
                if (ch == ':'
1✔
980
                        && ("xmlns".equals(name.substring(0, i))
1✔
981
                                || namespaces.contains(name.substring(0, i)))) {
1✔
982
                    result.append(ch);
1✔
983
                } else if (ch != ':') {
1✔
984
                    try {
985
                        DOCUMENT.createElement("a" + ch);
1✔
986
                        result.append(ch);
1✔
987
                    } catch (Exception ex) {
1✔
988
                        result.append("__")
1✔
989
                                .append(Base32.encode(Character.toString(ch)))
1✔
990
                                .append("__");
1✔
991
                    }
1✔
992
                } else {
993
                    result.append("__").append(Base32.encode(Character.toString(ch))).append("__");
1✔
994
                }
995
            }
996
            return result.toString();
1✔
997
        }
998

999
        public static String escape(String s) {
1000
            if (s == null) {
1✔
1001
                return "";
1✔
1002
            }
1003
            StringBuilder sb = new StringBuilder();
1✔
1004
            escape(s, sb);
1✔
1005
            return sb.toString();
1✔
1006
        }
1007

1008
        private static void escape(String s, StringBuilder sb) {
1009
            final int len = s.length();
1✔
1010
            for (int i = 0; i < len; i++) {
1✔
1011
                char ch = s.charAt(i);
1✔
1012
                switch (ch) {
1✔
1013
                    case '\'':
1014
                        sb.append("'");
1✔
1015
                        break;
1✔
1016
                    case '&':
1017
                        sb.append("&amp;");
1✔
1018
                        break;
1✔
1019
                    case '<':
1020
                        sb.append("&lt;");
1✔
1021
                        break;
1✔
1022
                    case '>':
1023
                        sb.append("&gt;");
1✔
1024
                        break;
1✔
1025
                    case '\b':
1026
                        sb.append("\\b");
1✔
1027
                        break;
1✔
1028
                    case '\f':
1029
                        sb.append("\\f");
1✔
1030
                        break;
1✔
1031
                    case '\n':
1032
                        sb.append("\n");
1✔
1033
                        break;
1✔
1034
                    case '\r':
1035
                        sb.append("&#xD;");
1✔
1036
                        break;
1✔
1037
                    case '\t':
1038
                        sb.append("\t");
1✔
1039
                        break;
1✔
1040
                    case '€':
1041
                        sb.append("€");
1✔
1042
                        break;
1✔
1043
                    default:
1044
                        if (ch <= '\u001F'
1✔
1045
                                || ch >= '\u007F' && ch <= '\u009F'
1046
                                || ch >= '\u2000' && ch <= '\u20FF') {
1047
                            String ss = Integer.toHexString(ch);
1✔
1048
                            sb.append("&#x");
1✔
1049
                            sb.append("0".repeat(4 - ss.length()));
1✔
1050
                            sb.append(ss.toUpperCase()).append(";");
1✔
1051
                        } else {
1✔
1052
                            sb.append(ch);
1✔
1053
                        }
1054
                        break;
1055
                }
1056
            }
1057
        }
1✔
1058

1059
        public static String unescape(String s) {
1060
            if (s == null) {
1✔
1061
                return "";
1✔
1062
            }
1063
            StringBuilder sb = new StringBuilder();
1✔
1064
            unescape(s, sb);
1✔
1065
            return sb.toString();
1✔
1066
        }
1067

1068
        private static void unescape(String s, StringBuilder sb) {
1069
            final int len = s.length();
1✔
1070
            final StringBuilder localSb = new StringBuilder();
1✔
1071
            int index = 0;
1✔
1072
            while (index < len) {
1✔
1073
                final int skipChars = translate(s, index, localSb);
1✔
1074
                if (skipChars > 0) {
1✔
1075
                    sb.append(localSb);
1✔
1076
                    localSb.setLength(0);
1✔
1077
                    index += skipChars;
1✔
1078
                } else {
1079
                    sb.append(s.charAt(index));
1✔
1080
                    index += 1;
1✔
1081
                }
1082
            }
1✔
1083
        }
1✔
1084

1085
        private static int translate(
1086
                final CharSequence input, final int index, final StringBuilder builder) {
1087
            final int shortest = 4;
1✔
1088
            final int longest = 6;
1✔
1089

1090
            if ('&' == input.charAt(index)) {
1✔
1091
                int max = longest;
1✔
1092
                if (index + longest > input.length()) {
1✔
1093
                    max = input.length() - index;
1✔
1094
                }
1095
                for (int i = max; i >= shortest; i--) {
1✔
1096
                    final CharSequence subSeq = input.subSequence(index, index + i);
1✔
1097
                    final String result = XML_UNESCAPE.get(subSeq.toString());
1✔
1098
                    if (result != null) {
1✔
1099
                        builder.append(result);
1✔
1100
                        return i;
1✔
1101
                    }
1102
                }
1103
            }
1104
            return 0;
1✔
1105
        }
1106

1107
        public static String getMapKey(Object map) {
1108
            return map instanceof Map && !((Map) map).isEmpty()
1✔
1109
                    ? String.valueOf(
1✔
1110
                            ((Map.Entry) ((Map) map).entrySet().iterator().next()).getKey())
1✔
1111
                    : "";
1✔
1112
        }
1113

1114
        public static Object getMapValue(Object map) {
1115
            return map instanceof Map && !((Map) map).isEmpty()
1✔
1116
                    ? ((Map.Entry) ((Map) map).entrySet().iterator().next()).getValue()
1✔
1117
                    : null;
1✔
1118
        }
1119
    }
1120

1121
    public static String toXml(Collection collection, XmlStringBuilder.Step identStep) {
1122
        final XmlStringBuilder builder =
1✔
1123
                new XmlStringBuilderWithoutRoot(identStep, UTF_8.name(), "");
1✔
1124
        writeArray(collection, builder, ARRAY_TRUE);
1✔
1125
        return builder.toString();
1✔
1126
    }
1127

1128
    public static String toXmlWithoutRoot(Collection collection, XmlStringBuilder.Step identStep) {
1129
        final XmlStringBuilder builder = new XmlStringBuilderWithoutHeader(identStep, 0);
1✔
1130
        writeArray(collection, builder, ARRAY_TRUE);
1✔
1131
        return builder.toString();
1✔
1132
    }
1133

1134
    public static String toXml(Collection collection) {
1135
        return toXml(collection, XmlStringBuilder.Step.TWO_SPACES);
1✔
1136
    }
1137

1138
    public static String toXml(Map map, XmlStringBuilder.Step identStep) {
1139
        return toXml(map, identStep, ROOT, ArrayTrue.ADD);
1✔
1140
    }
1141

1142
    public static String toXml(Map map, XmlStringBuilder.Step identStep, String newRootName) {
1143
        return toXml(map, identStep, newRootName, ArrayTrue.ADD);
1✔
1144
    }
1145

1146
    public static String toXml(
1147
            Map map, XmlStringBuilder.Step identStep, String newRootName, ArrayTrue arrayTrue) {
1148
        final XmlStringBuilder builder;
1149
        final Map localMap;
1150
        if (map != null && map.containsKey(ENCODING)) {
1✔
1151
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1152
            builder =
1✔
1153
                    checkStandalone(String.valueOf(localMap.remove(ENCODING)), identStep, localMap);
1✔
1154
        } else if (map != null && map.containsKey(STANDALONE)) {
1✔
1155
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1156
            builder =
1✔
1157
                    new XmlStringBuilderWithoutRoot(
1158
                            identStep,
1159
                            UTF_8.name(),
1✔
1160
                            " standalone=\""
1161
                                    + (YES.equals(map.get(STANDALONE)) ? YES : "no")
1✔
1162
                                    + "\"");
1163
            localMap.remove(STANDALONE);
1✔
1164
        } else if (map != null && map.containsKey(OMITXMLDECLARATION)) {
1✔
1165
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1166
            builder = new XmlStringBuilderWithoutHeader(identStep, 0);
1✔
1167
            localMap.remove(OMITXMLDECLARATION);
1✔
1168
        } else {
1169
            builder = new XmlStringBuilderWithoutRoot(identStep, UTF_8.name(), "");
1✔
1170
            localMap = map;
1✔
1171
        }
1172
        checkLocalMap(builder, localMap, newRootName, arrayTrue == ArrayTrue.ADD ? ARRAY_TRUE : "");
1✔
1173
        return builder.toString();
1✔
1174
    }
1175

1176
    private static void checkLocalMap(
1177
            final XmlStringBuilder builder,
1178
            final Map localMap,
1179
            final String newRootName,
1180
            final String arrayTrue) {
1181
        final Map localMap2;
1182
        if (localMap != null && localMap.containsKey(DOCTYPE_TEXT)) {
1✔
1183
            localMap2 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1184
            localMap2.remove(DOCTYPE_TEXT);
1✔
1185
            builder.append(DOCTYPE_HEADER)
1✔
1186
                    .append(String.valueOf(localMap.get(DOCTYPE_TEXT)))
1✔
1187
                    .append(">")
1✔
1188
                    .newLine();
1✔
1189
        } else {
1190
            localMap2 = localMap;
1✔
1191
        }
1192
        if (localMap2 == null
1✔
1193
                || localMap2.size() != 1
1✔
1194
                || XmlValue.getMapKey(localMap2).startsWith("-")
1✔
1195
                || XmlValue.getMapValue(localMap2) instanceof List) {
1✔
1196
            if (ROOT.equals(XmlValue.getMapKey(localMap2))) {
1✔
1197
                writeArray((List) XmlValue.getMapValue(localMap2), builder, arrayTrue);
1✔
1198
            } else {
1199
                XmlObject.writeXml(
1✔
1200
                        localMap2,
1201
                        getRootName(localMap2, newRootName),
1✔
1202
                        builder,
1203
                        false,
1204
                        new LinkedHashSet<>(),
1205
                        false,
1206
                        arrayTrue);
1207
            }
1208
        } else {
1209
            XmlObject.writeXml(
1✔
1210
                    localMap2,
1211
                    getRootName(localMap2, newRootName),
1✔
1212
                    builder,
1213
                    false,
1214
                    new LinkedHashSet<>(),
1215
                    false,
1216
                    arrayTrue);
1217
        }
1218
    }
1✔
1219

1220
    private static void writeArray(
1221
            final Collection collection, final XmlStringBuilder builder, final String arrayTrue) {
1222
        builder.append("<root");
1✔
1223
        if (collection != null && collection.isEmpty()) {
1✔
1224
            builder.append(" empty-array=\"true\"");
1✔
1225
        }
1226
        builder.append(">").incIdent();
1✔
1227
        if (collection != null && !collection.isEmpty()) {
1✔
1228
            builder.newLine();
1✔
1229
        }
1230
        XmlArray.writeXml(
1✔
1231
                collection, null, builder, false, new LinkedHashSet<>(), false, arrayTrue);
1232
        if (collection != null && !collection.isEmpty()) {
1✔
1233
            builder.newLine();
1✔
1234
        }
1235
        builder.append("</root>");
1✔
1236
    }
1✔
1237

1238
    private static XmlStringBuilder checkStandalone(
1239
            String encoding, XmlStringBuilder.Step identStep, final Map localMap) {
1240
        final XmlStringBuilder builder;
1241
        if (localMap.containsKey(STANDALONE)) {
1✔
1242
            builder =
1✔
1243
                    new XmlStringBuilderWithoutRoot(
1244
                            identStep,
1245
                            encoding,
1246
                            " standalone=\""
1247
                                    + (YES.equals(localMap.get(STANDALONE)) ? YES : "no")
1✔
1248
                                    + "\"");
1249
            localMap.remove(STANDALONE);
1✔
1250
        } else {
1251
            builder = new XmlStringBuilderWithoutRoot(identStep, encoding, "");
1✔
1252
        }
1253
        return builder;
1✔
1254
    }
1255

1256
    @SuppressWarnings("unchecked")
1257
    private static String getRootName(final Map localMap, final String newRootName) {
1258
        int foundAttrs = 0;
1✔
1259
        int foundElements = 0;
1✔
1260
        int foundListElements = 0;
1✔
1261
        if (localMap != null) {
1✔
1262
            for (Map.Entry entry : (Set<Map.Entry>) localMap.entrySet()) {
1✔
1263
                if (String.valueOf(entry.getKey()).startsWith("-")) {
1✔
1264
                    foundAttrs += 1;
1✔
1265
                } else if (!String.valueOf(entry.getKey()).startsWith(COMMENT)
1✔
1266
                        && !String.valueOf(entry.getKey()).startsWith(CDATA)
1✔
1267
                        && !String.valueOf(entry.getKey()).startsWith("?")) {
1✔
1268
                    if (entry.getValue() instanceof List && ((List) entry.getValue()).size() > 1) {
1✔
1269
                        foundListElements += 1;
1✔
1270
                    }
1271
                    foundElements += 1;
1✔
1272
                }
1273
            }
1✔
1274
        }
1275
        return foundAttrs == 0 && foundElements == 1 && foundListElements == 0 ? null : newRootName;
1✔
1276
    }
1277

1278
    public static String toXml(Map map) {
1279
        return toXml(map, XmlStringBuilder.Step.TWO_SPACES, ROOT);
1✔
1280
    }
1281

1282
    @SuppressWarnings("unchecked")
1283
    private static Object getValue(final String name, final Object value, final FromType fromType) {
1284
        final Object localValue;
1285
        if (value instanceof Map && ((Map<String, Object>) value).entrySet().size() == 1) {
1✔
1286
            final Map.Entry<String, Object> entry =
1✔
1287
                    ((Map<String, Object>) value).entrySet().iterator().next();
1✔
1288
            if (TEXT.equals(entry.getKey())
1✔
1289
                    || (fromType == FromType.FOR_CONVERT && ELEMENT_TEXT.equals(entry.getKey()))) {
1✔
1290
                localValue = entry.getValue();
1✔
1291
            } else {
1292
                localValue = value;
1✔
1293
            }
1294
        } else {
1✔
1295
            localValue = value;
1✔
1296
        }
1297
        return localValue instanceof String && name.startsWith("-")
1✔
1298
                ? XmlValue.unescape((String) localValue)
1✔
1299
                : localValue;
1✔
1300
    }
1301

1302
    public static Object stringToNumber(String number) {
1303
        final Object localValue;
1304
        if (number.contains(".") || number.contains("e") || number.contains("E")) {
1✔
1305
            if (number.length() > 9
1✔
1306
                    || (number.contains(".") && number.length() - number.lastIndexOf('.') > 2)
1✔
1307
                            && number.charAt(number.length() - 1) == '0') {
1✔
1308
                localValue = new java.math.BigDecimal(number);
1✔
1309
            } else {
1310
                localValue = Double.valueOf(number);
1✔
1311
            }
1312
        } else {
1313
            if (number.length() > 19) {
1✔
1314
                localValue = new java.math.BigInteger(number);
1✔
1315
            } else {
1316
                localValue = Long.valueOf(number);
1✔
1317
            }
1318
        }
1319
        return localValue;
1✔
1320
    }
1321

1322
    private static Object createMap(
1323
            final org.w3c.dom.Node node,
1324
            final BiFunction<Object, Set<String>, String> elementMapper,
1325
            final Function<Object, Object> nodeMapper,
1326
            final Map<String, Object> attrMap,
1327
            final int[] uniqueIds,
1328
            final String source,
1329
            final int[] sourceIndex,
1330
            final Set<String> namespaces,
1331
            final FromType fromType) {
1332
        final Map<String, Object> map = new LinkedHashMap<>(attrMap);
1✔
1333
        final org.w3c.dom.NodeList nodeList = node.getChildNodes();
1✔
1334
        for (int index = 0; index < nodeList.getLength(); index++) {
1✔
1335
            final org.w3c.dom.Node currentNode = nodeList.item(index);
1✔
1336
            final String name;
1337
            if (currentNode.getNodeType() == org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE) {
1✔
1338
                name = "?" + currentNode.getNodeName();
1✔
1339
            } else {
1340
                name = currentNode.getNodeName();
1✔
1341
            }
1342
            final Object value;
1343
            if (currentNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
1✔
1344
                sourceIndex[0] = source.indexOf("<" + name, sourceIndex[0]) + name.length() + 1;
1✔
1345
                value =
1✔
1346
                        addElement(
1✔
1347
                                sourceIndex,
1348
                                source,
1349
                                elementMapper,
1350
                                nodeMapper,
1351
                                uniqueIds,
1352
                                currentNode,
1353
                                namespaces,
1354
                                fromType);
1355
            } else {
1356
                if (COMMENT.equals(name)) {
1✔
1357
                    sourceIndex[0] = source.indexOf("-->", sourceIndex[0]) + 3;
1✔
1358
                } else if (CDATA.equals(name)) {
1✔
1359
                    sourceIndex[0] = source.indexOf("]]>", sourceIndex[0]) + 3;
1✔
1360
                }
1361
                value = currentNode.getTextContent();
1✔
1362
            }
1363
            if (TEXT.equals(name)
1✔
1364
                    && node.getChildNodes().getLength() > 1
1✔
1365
                    && String.valueOf(value).trim().isEmpty()) {
1✔
1366
                continue;
1✔
1367
            }
1368
            if (currentNode.getNodeType() == org.w3c.dom.Node.DOCUMENT_TYPE_NODE) {
1✔
1369
                addNodeValue(
1✔
1370
                        map,
1371
                        DOCTYPE_TEXT,
1372
                        getDoctypeValue(source),
1✔
1373
                        elementMapper,
1374
                        nodeMapper,
1375
                        uniqueIds,
1376
                        namespaces,
1377
                        fromType);
1378
            } else {
1379
                addNodeValue(
1✔
1380
                        map,
1381
                        name,
1382
                        value,
1383
                        elementMapper,
1384
                        nodeMapper,
1385
                        uniqueIds,
1386
                        namespaces,
1387
                        fromType);
1388
            }
1389
        }
1390
        return checkNumberAndBoolean(map, node.getNodeName());
1✔
1391
    }
1392

1393
    @SuppressWarnings("unchecked")
1394
    private static Object checkNumberAndBoolean(final Map<String, Object> map, final String name) {
1395
        final Map<String, Object> localMap;
1396
        if (map.containsKey(NUMBER) && TRUE.equals(map.get(NUMBER)) && map.containsKey(TEXT)) {
1✔
1397
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1398
            localMap.remove(NUMBER);
1✔
1399
            localMap.put(TEXT, stringToNumber(String.valueOf(localMap.get(TEXT))));
1✔
1400
        } else {
1401
            localMap = map;
1✔
1402
        }
1403
        final Map<String, Object> localMap2;
1404
        if (map.containsKey(BOOLEAN) && TRUE.equals(map.get(BOOLEAN)) && map.containsKey(TEXT)) {
1✔
1405
            localMap2 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1406
            localMap2.remove(BOOLEAN);
1✔
1407
            localMap2.put(TEXT, Boolean.valueOf(String.valueOf(localMap.get(TEXT))));
1✔
1408
        } else {
1409
            localMap2 = localMap;
1✔
1410
        }
1411
        return checkArray(localMap2, name);
1✔
1412
    }
1413

1414
    @SuppressWarnings("unchecked")
1415
    private static Object checkArray(final Map<String, Object> map, final String name) {
1416
        final Map<String, Object> localMap = checkNullAndString(map);
1✔
1417
        final Object object;
1418
        if (map.containsKey(ARRAY) && TRUE.equals(map.get(ARRAY))) {
1✔
1419
            final Map<String, Object> localMap4 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1420
            localMap4.remove(ARRAY);
1✔
1421
            localMap4.remove(SELF_CLOSING);
1✔
1422
            object =
1423
                    name.equals(XmlValue.getMapKey(localMap4))
1✔
1424
                            ? new ArrayList<>(
1✔
1425
                                    Collections.singletonList(
1✔
1426
                                            getValue(
1✔
1427
                                                    name,
1428
                                                    XmlValue.getMapValue(localMap4),
1✔
1429
                                                    FromType.FOR_CONVERT)))
1430
                            : new ArrayList<>(
1✔
1431
                                    Collections.singletonList(
1✔
1432
                                            getValue(name, localMap4, FromType.FOR_CONVERT)));
1✔
1433
        } else {
1✔
1434
            object = localMap;
1✔
1435
        }
1436
        final Object object2;
1437
        if (map.containsKey(EMPTY_ARRAY) && TRUE.equals(map.get(EMPTY_ARRAY))) {
1✔
1438
            final Map<String, Object> localMap4 = (Map) ((LinkedHashMap) map).clone();
1✔
1439
            localMap4.remove(EMPTY_ARRAY);
1✔
1440
            if (localMap4.containsKey(ARRAY)
1✔
1441
                    && TRUE.equals(localMap4.get(ARRAY))
1✔
1442
                    && localMap4.size() == 1) {
1✔
1443
                object2 = new ArrayList<>();
1✔
1444
                ((List) object2).add(new ArrayList<>());
1✔
1445
            } else {
1446
                object2 = localMap4.isEmpty() ? new ArrayList<>() : localMap4;
1✔
1447
            }
1448
        } else {
1✔
1449
            object2 = object;
1✔
1450
        }
1451
        return object2;
1✔
1452
    }
1453

1454
    @SuppressWarnings("unchecked")
1455
    private static Map<String, Object> checkNullAndString(final Map<String, Object> map) {
1456
        final Map<String, Object> localMap;
1457
        if (map.containsKey(NULL_ATTR) && TRUE.equals(map.get(NULL_ATTR))) {
1✔
1458
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1459
            localMap.remove(NULL_ATTR);
1✔
1460
            if (!map.containsKey(TEXT)) {
1✔
1461
                localMap.put(TEXT, null);
1✔
1462
            }
1463
        } else {
1464
            localMap = map;
1✔
1465
        }
1466
        final Map<String, Object> localMap2;
1467
        if (map.containsKey(STRING) && TRUE.equals(map.get(STRING))) {
1✔
1468
            localMap2 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1469
            localMap2.remove(STRING);
1✔
1470
            if (!map.containsKey(TEXT)) {
1✔
1471
                localMap2.put(TEXT, "");
1✔
1472
            }
1473
        } else {
1474
            localMap2 = localMap;
1✔
1475
        }
1476
        return localMap2;
1✔
1477
    }
1478

1479
    private static Object addElement(
1480
            final int[] sourceIndex,
1481
            final String source,
1482
            final BiFunction<Object, Set<String>, String> elementMapper,
1483
            final Function<Object, Object> nodeMapper,
1484
            final int[] uniqueIds,
1485
            final org.w3c.dom.Node currentNode,
1486
            final Set<String> namespaces,
1487
            final FromType fromType) {
1488
        final Map<String, Object> attrMapLocal = new LinkedHashMap<>();
1✔
1489
        if (currentNode.getAttributes().getLength() > 0) {
1✔
1490
            final Map<String, String> attributes =
1✔
1491
                    parseAttributes(getAttributes(sourceIndex[0], source));
1✔
1492
            for (Map.Entry<String, String> attribute : attributes.entrySet()) {
1✔
1493
                if (attribute.getKey().startsWith("xmlns:")) {
1✔
1494
                    namespaces.add(attribute.getKey().substring(6));
1✔
1495
                }
1496
            }
1✔
1497
            for (Map.Entry<String, String> attribute : attributes.entrySet()) {
1✔
1498
                addNodeValue(
1✔
1499
                        attrMapLocal,
1500
                        '-' + attribute.getKey(),
1✔
1501
                        attribute.getValue(),
1✔
1502
                        elementMapper,
1503
                        nodeMapper,
1504
                        uniqueIds,
1505
                        namespaces,
1506
                        fromType);
1507
            }
1✔
1508
        }
1509
        if (getAttributes(sourceIndex[0], source).endsWith("/")
1✔
1510
                && !attrMapLocal.containsKey(SELF_CLOSING)
1✔
1511
                && (attrMapLocal.size() != 1
1✔
1512
                        || ((!attrMapLocal.containsKey(STRING)
1✔
1513
                                        || !TRUE.equals(attrMapLocal.get(STRING)))
1✔
1514
                                && (!attrMapLocal.containsKey(NULL_ATTR)
1✔
1515
                                        || !TRUE.equals(attrMapLocal.get(NULL_ATTR)))))) {
1✔
1516
            attrMapLocal.put(SELF_CLOSING, TRUE);
1✔
1517
        }
1518
        return createMap(
1✔
1519
                currentNode,
1520
                elementMapper,
1521
                nodeMapper,
1522
                attrMapLocal,
1523
                uniqueIds,
1524
                source,
1525
                sourceIndex,
1526
                namespaces,
1527
                fromType);
1528
    }
1529

1530
    static Map<String, String> parseAttributes(final String source) {
1531
        Map<String, String> result = new LinkedHashMap<>();
1✔
1532
        StringBuilder key = new StringBuilder();
1✔
1533
        StringBuilder value = new StringBuilder();
1✔
1534
        boolean inQuotes = false;
1✔
1535
        boolean expectingValue = false;
1✔
1536
        for (char c : source.toCharArray()) {
1✔
1537
            if (c == '"') {
1✔
1538
                inQuotes = !inQuotes;
1✔
1539
                if (!inQuotes && expectingValue) {
1✔
1540
                    result.put(key.toString(), value.toString());
1✔
1541
                    key.setLength(0);
1✔
1542
                    value.setLength(0);
1✔
1543
                    expectingValue = false;
1✔
1544
                }
1545
            } else if (c == '=' && !inQuotes) {
1✔
1546
                expectingValue = true;
1✔
1547
            } else if (inQuotes) {
1✔
1548
                value.append(c);
1✔
1549
            } else if (!SKIPPED_CHARS.contains(c)) {
1✔
1550
                key.append(c);
1✔
1551
            }
1552
        }
1553
        return result;
1✔
1554
    }
1555

1556
    static String getAttributes(final int sourceIndex, final String source) {
1557
        boolean scanQuote = false;
1✔
1558
        for (int index = sourceIndex; index < source.length(); index += 1) {
1✔
1559
            if (source.charAt(index) == '"') {
1✔
1560
                scanQuote = !scanQuote;
1✔
1561
                continue;
1✔
1562
            }
1563
            if (!scanQuote && source.charAt(index) == '>') {
1✔
1564
                return source.substring(sourceIndex, index);
1✔
1565
            }
1566
        }
1567
        return "";
1✔
1568
    }
1569

1570
    private static String unescapeName(final String name) {
1571
        if (name == null) {
1✔
1572
            return null;
1✔
1573
        }
1574
        final int length = name.length();
1✔
1575
        if ("__EE__EMPTY__EE__".equals(name)) {
1✔
1576
            return "";
1✔
1577
        }
1578
        if ("-__EE__EMPTY__EE__".equals(name)) {
1✔
1579
            return "-";
1✔
1580
        }
1581
        if (!name.contains("__")) {
1✔
1582
            return name;
1✔
1583
        }
1584
        StringBuilder result = new StringBuilder();
1✔
1585
        int underlineCount = 0;
1✔
1586
        StringBuilder lastChars = new StringBuilder();
1✔
1587
        int i = 0;
1✔
1588
        outer:
1589
        while (i < length) {
1✔
1590
            char ch = name.charAt(i);
1✔
1591
            if (ch == '_') {
1✔
1592
                lastChars.append(ch);
1✔
1593
            } else {
1594
                if (lastChars.length() == 2) {
1✔
1595
                    StringBuilder nameToDecode = new StringBuilder();
1✔
1596
                    for (int j = i; j < length; ++j) {
1✔
1597
                        if (name.charAt(j) == '_') {
1✔
1598
                            underlineCount += 1;
1✔
1599
                            if (underlineCount == 2) {
1✔
1600
                                try {
1601
                                    result.append(Base32.decode(nameToDecode.toString()));
1✔
1602
                                } catch (Base32.DecodingException ex) {
1✔
1603
                                    result.append("__").append(nameToDecode).append(lastChars);
1✔
1604
                                }
1✔
1605
                                i = j;
1✔
1606
                                underlineCount = 0;
1✔
1607
                                lastChars.setLength(0);
1✔
1608
                                i++;
1✔
1609
                                continue outer;
1✔
1610
                            }
1611
                        } else {
1612
                            nameToDecode.append(name.charAt(j));
1✔
1613
                            underlineCount = 0;
1✔
1614
                        }
1615
                    }
1616
                }
1617
                result.append(lastChars).append(ch);
1✔
1618
                lastChars.setLength(0);
1✔
1619
            }
1620
            i++;
1✔
1621
        }
1✔
1622
        return result.append(lastChars).toString();
1✔
1623
    }
1624

1625
    @SuppressWarnings("unchecked")
1626
    private static void addNodeValue(
1627
            final Map<String, Object> map,
1628
            final String name,
1629
            final Object value,
1630
            final BiFunction<Object, Set<String>, String> elementMapper,
1631
            final Function<Object, Object> nodeMapper,
1632
            final int[] uniqueIds,
1633
            final Set<String> namespaces,
1634
            final FromType fromType) {
1635
        final String elementName = unescapeName(elementMapper.apply(name, namespaces));
1✔
1636
        if (map.containsKey(elementName)) {
1✔
1637
            if (TEXT.equals(elementName)) {
1✔
1638
                map.put(
1✔
1639
                        elementName + uniqueIds[0],
1640
                        nodeMapper.apply(getValue(name, value, fromType)));
1✔
1641
                uniqueIds[0] += 1;
1✔
1642
            } else if (COMMENT.equals(elementName)) {
1✔
1643
                map.put(
1✔
1644
                        elementName + uniqueIds[1],
1645
                        nodeMapper.apply(getValue(name, value, fromType)));
1✔
1646
                uniqueIds[1] += 1;
1✔
1647
            } else if (CDATA.equals(elementName)) {
1✔
1648
                map.put(
1✔
1649
                        elementName + uniqueIds[2],
1650
                        nodeMapper.apply(getValue(name, value, fromType)));
1✔
1651
                uniqueIds[2] += 1;
1✔
1652
            } else {
1653
                final Object object = map.get(elementName);
1✔
1654
                if (object instanceof List) {
1✔
1655
                    addText(map, elementName, (List<Object>) object, value, fromType);
1✔
1656
                } else {
1657
                    final List<Object> objects = new ArrayList<>();
1✔
1658
                    objects.add(object);
1✔
1659
                    addText(map, elementName, objects, value, fromType);
1✔
1660
                    map.put(elementName, objects);
1✔
1661
                }
1662
            }
1✔
1663
        } else {
1664
            if (elementName != null) {
1✔
1665
                map.put(elementName, nodeMapper.apply(getValue(name, value, fromType)));
1✔
1666
            }
1667
        }
1668
    }
1✔
1669

1670
    private static void addText(
1671
            final Map<String, Object> map,
1672
            final String name,
1673
            final List<Object> objects,
1674
            final Object value,
1675
            final FromType fromType) {
1676
        int lastIndex = map.size() - 1;
1✔
1677
        final int index = objects.size();
1✔
1678
        while (true) {
1679
            final Map.Entry lastElement = (Map.Entry) map.entrySet().toArray()[lastIndex];
1✔
1680
            if (name.equals(String.valueOf(lastElement.getKey()))) {
1✔
1681
                break;
1✔
1682
            }
1683
            final Map<String, Object> item = new LinkedHashMap<>();
1✔
1684
            final Map<String, Object> text = new LinkedHashMap<>();
1✔
1685
            text.put(String.valueOf(lastElement.getKey()), map.remove(lastElement.getKey()));
1✔
1686
            item.put("#item", text);
1✔
1687
            objects.add(index, item);
1✔
1688
            lastIndex -= 1;
1✔
1689
        }
1✔
1690
        final Object newValue = getValue(name, value, fromType);
1✔
1691
        if (newValue instanceof List) {
1✔
1692
            objects.add(((List) newValue).get(0));
1✔
1693
        } else {
1694
            objects.add(newValue);
1✔
1695
        }
1696
    }
1✔
1697

1698
    public static Object fromXml(final String xml) {
1699
        return fromXml(xml, FromType.FOR_CONVERT);
1✔
1700
    }
1701

1702
    public enum FromType {
1✔
1703
        FOR_CONVERT,
1✔
1704
        FOR_FORMAT
1✔
1705
    }
1706

1707
    public static Object fromXml(final String xml, final FromType fromType) {
1708
        if (xml == null) {
1✔
1709
            return null;
1✔
1710
        }
1711
        try {
1712
            org.w3c.dom.Document document = Document.createDocument(xml);
1✔
1713
            final Object result =
1✔
1714
                    createMap(
1✔
1715
                            document,
1716
                            (object, namespaces) -> String.valueOf(object),
1✔
1717
                            object -> object,
1✔
1718
                            Collections.emptyMap(),
1✔
1719
                            new int[] {1, 1, 1},
1720
                            xml,
1721
                            new int[] {0},
1722
                            new LinkedHashSet<>(),
1723
                            fromType);
1724
            if (checkResult(xml, document, result, fromType)) {
1✔
1725
                return ((Map.Entry) ((Map) result).entrySet().iterator().next()).getValue();
1✔
1726
            }
1727
            return result;
1✔
1728
        } catch (Exception ex) {
1✔
1729
            throw new IllegalArgumentException(ex);
1✔
1730
        }
1731
    }
1732

1733
    @SuppressWarnings("unchecked")
1734
    private static boolean checkResult(
1735
            final String xml,
1736
            org.w3c.dom.Document document,
1737
            final Object result,
1738
            final FromType fromType) {
1739
        final Map<String, String> headerAttributes = getHeaderAttributes(xml);
1✔
1740
        if (document.getXmlEncoding() != null
1✔
1741
                && !"UTF-8".equalsIgnoreCase(document.getXmlEncoding())) {
1✔
1742
            ((Map) result).put(ENCODING, document.getXmlEncoding());
1✔
1743
            if (headerAttributes.containsKey(STANDALONE.substring(1))) {
1✔
1744
                ((Map) result).put(STANDALONE, headerAttributes.get(STANDALONE.substring(1)));
1✔
1745
            }
1746
        } else if (headerAttributes.containsKey(STANDALONE.substring(1))) {
1✔
1747
            ((Map) result).put(STANDALONE, headerAttributes.get(STANDALONE.substring(1)));
1✔
1748
        } else if (fromType == FromType.FOR_CONVERT
1✔
1749
                && Xml.XmlValue.getMapKey(result).equals(ROOT)
1✔
1750
                && (Xml.XmlValue.getMapValue(result) instanceof List
1✔
1751
                        || Xml.XmlValue.getMapValue(result) instanceof Map)) {
1✔
1752
            if (xml.startsWith(XML_HEADER)) {
1✔
1753
                return true;
1✔
1754
            } else {
1755
                ((Map) result).put(OMITXMLDECLARATION, YES);
1✔
1756
            }
1757
        } else if (!xml.startsWith(XML_HEADER)) {
1✔
1758
            ((Map) result).put(OMITXMLDECLARATION, YES);
1✔
1759
        }
1760
        return false;
1✔
1761
    }
1762

1763
    private static Map<String, String> getHeaderAttributes(final String xml) {
1764
        final Map<String, String> result = new LinkedHashMap<>();
1✔
1765
        if (xml.startsWith(XML_HEADER)) {
1✔
1766
            final String xmlLocal =
1✔
1767
                    xml.substring(
1✔
1768
                            XML_HEADER.length(),
1✔
1769
                            Math.max(XML_HEADER.length(), xml.indexOf("?>", XML_HEADER.length())));
1✔
1770
            final Map<String, String> attributes = parseAttributes(xmlLocal);
1✔
1771
            result.putAll(attributes);
1✔
1772
        }
1773
        return result;
1✔
1774
    }
1775

1776
    static String getDoctypeValue(final String xml) {
1777
        int startIndex = xml.indexOf(DOCTYPE_HEADER) + DOCTYPE_HEADER.length();
1✔
1778
        char charToFind = '>';
1✔
1779
        int endIndexPlus = 0;
1✔
1780
        for (int endIndex = startIndex; endIndex < xml.length(); endIndex += 1) {
1✔
1781
            if (xml.charAt(endIndex) == '[') {
1✔
1782
                charToFind = ']';
1✔
1783
                endIndexPlus = 1;
1✔
1784
                continue;
1✔
1785
            }
1786
            if (xml.charAt(endIndex) == charToFind) {
1✔
1787
                return xml.substring(startIndex, endIndex + endIndexPlus);
1✔
1788
            }
1789
        }
1790
        return "";
1✔
1791
    }
1792

1793
    private static class MyEntityResolver implements org.xml.sax.EntityResolver {
1794
        public org.xml.sax.InputSource resolveEntity(String publicId, String systemId) {
1795
            return new org.xml.sax.InputSource(new java.io.StringReader(""));
1✔
1796
        }
1797
    }
1798

1799
    protected static class Document {
1800
        private Document() {}
1801

1802
        public static org.w3c.dom.Document createDocument(final String xml)
1803
                throws java.io.IOException, javax.xml.parsers.ParserConfigurationException,
1804
                        org.xml.sax.SAXException {
1805
            final javax.xml.parsers.DocumentBuilderFactory factory =
1806
                    javax.xml.parsers.DocumentBuilderFactory.newInstance();
1807
            factory.setNamespaceAware(true);
1808
            try {
1809
                factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
1810
            } catch (Exception ignored) {
1811
                // ignored
1812
            }
1813
            final javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
1814
            builder.setErrorHandler(new org.xml.sax.helpers.DefaultHandler());
1815
            builder.setEntityResolver(new MyEntityResolver());
1816
            return builder.parse(new org.xml.sax.InputSource(new java.io.StringReader(xml)));
1817
        }
1818

1819
        private static org.w3c.dom.Document createDocument() {
1820
            try {
1821
                final javax.xml.parsers.DocumentBuilderFactory factory =
1822
                        javax.xml.parsers.DocumentBuilderFactory.newInstance();
1823
                factory.setNamespaceAware(true);
1824
                setupFactory(factory);
1825
                final javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
1826
                return builder.newDocument();
1827
            } catch (javax.xml.parsers.ParserConfigurationException ex) {
1828
                throw new IllegalArgumentException(ex);
1829
            }
1830
        }
1831

1832
        private static void setupFactory(javax.xml.parsers.DocumentBuilderFactory factory) {
1833
            try {
1834
                factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
1835
            } catch (Exception ignored) {
1836
                // ignored
1837
            }
1838
        }
1839
    }
1840

1841
    public static Object fromXmlMakeArrays(final String xml) {
1842
        try {
1843
            org.w3c.dom.Document document = Document.createDocument(xml);
1✔
1844
            final Object result =
1✔
1845
                    createMap(
1✔
1846
                            document,
1847
                            (object, namespaces) -> String.valueOf(object),
1✔
1848
                            object ->
1849
                                    object instanceof List
1✔
1850
                                            ? object
1✔
1851
                                            : new ArrayList<>(Collections.singletonList(object)),
1✔
1852
                            Collections.emptyMap(),
1✔
1853
                            new int[] {1, 1, 1},
1854
                            xml,
1855
                            new int[] {0},
1856
                            new LinkedHashSet<>(),
1857
                            FromType.FOR_CONVERT);
1858
            if (checkResult(xml, document, result, FromType.FOR_CONVERT)) {
1✔
1859
                return ((Map.Entry) ((Map) result).entrySet().iterator().next()).getValue();
1✔
1860
            }
1861
            return result;
1✔
1862
        } catch (Exception ex) {
1✔
1863
            throw new IllegalArgumentException(ex);
1✔
1864
        }
1865
    }
1866

1867
    public static Object fromXmlWithElementMapper(
1868
            final String xml, final BiFunction<Object, Set<String>, String> elementMapper) {
1869
        try {
1870
            org.w3c.dom.Document document = Document.createDocument(xml);
1✔
1871
            final Object result =
1✔
1872
                    createMap(
1✔
1873
                            document,
1874
                            elementMapper,
1875
                            object -> object,
1✔
1876
                            Collections.emptyMap(),
1✔
1877
                            new int[] {1, 1, 1},
1878
                            xml,
1879
                            new int[] {0},
1880
                            new LinkedHashSet<>(),
1881
                            FromType.FOR_CONVERT);
1882
            if (checkResult(xml, document, result, FromType.FOR_CONVERT)) {
1✔
1883
                return ((Map.Entry) ((Map) result).entrySet().iterator().next()).getValue();
1✔
1884
            }
1885
            return result;
1✔
1886
        } catch (Exception ex) {
1✔
1887
            throw new IllegalArgumentException(ex);
1✔
1888
        }
1889
    }
1890

1891
    public static Object fromXmlWithoutNamespaces(final String xml) {
1892
        return fromXmlWithElementMapper(
1✔
1893
                xml,
1894
                (object, namespaces) -> {
1895
                    final String localString = String.valueOf(object);
1✔
1896
                    final String result;
1897
                    String substring =
1✔
1898
                            localString.substring(Math.max(0, localString.indexOf(':') + 1));
1✔
1899
                    if (localString.startsWith("-")
1✔
1900
                            && namespaces.contains(
1✔
1901
                                    localString.substring(
1✔
1902
                                            1, Math.max(1, localString.indexOf(':'))))) {
1✔
1903
                        result = "-" + substring;
1✔
1904
                    } else if (namespaces.contains(
1✔
1905
                            localString.substring(0, Math.max(0, localString.indexOf(':'))))) {
1✔
1906
                        result = substring;
1✔
1907
                    } else {
1908
                        result = String.valueOf(object);
1✔
1909
                    }
1910
                    return result;
1✔
1911
                });
1912
    }
1913

1914
    public static Object fromXmlWithoutAttributes(final String xml) {
1915
        return fromXmlWithElementMapper(
1✔
1916
                xml,
1917
                (object, namespaces) ->
1918
                        String.valueOf(object).startsWith("-") ? null : String.valueOf(object));
1✔
1919
    }
1920

1921
    public static Object fromXmlWithoutNamespacesAndAttributes(final String xml) {
1922
        return fromXmlWithElementMapper(
1✔
1923
                xml,
1924
                (object, namespaces) -> {
1925
                    final String localString = String.valueOf(object);
1✔
1926
                    final String result;
1927
                    if (localString.startsWith("-")) {
1✔
1928
                        result = null;
1✔
1929
                    } else if (namespaces.contains(
1✔
1930
                            localString.substring(0, Math.max(0, localString.indexOf(':'))))) {
1✔
1931
                        result = localString.substring(Math.max(0, localString.indexOf(':') + 1));
1✔
1932
                    } else {
1933
                        result = String.valueOf(object);
1✔
1934
                    }
1935
                    return result;
1✔
1936
                });
1937
    }
1938

1939
    public static String formatXml(String xml, XmlStringBuilder.Step identStep) {
1940
        Object result = fromXml(xml, FromType.FOR_FORMAT);
1✔
1941
        return toXml((Map) result, identStep, ROOT);
1✔
1942
    }
1943

1944
    public static String formatXml(String xml) {
1945
        return formatXml(xml, XmlStringBuilder.Step.TWO_SPACES);
1✔
1946
    }
1947

1948
    @SuppressWarnings("unchecked")
1949
    public static String changeXmlEncoding(
1950
            String xml, XmlStringBuilder.Step identStep, String encoding) {
1951
        Object result = fromXml(xml, FromType.FOR_FORMAT);
1✔
1952
        if (result instanceof Map) {
1✔
1953
            ((Map) result).put(ENCODING, encoding);
1✔
1954
            return toXml((Map) result, identStep, ROOT);
1✔
1955
        }
1956
        return xml;
1✔
1957
    }
1958

1959
    public static String changeXmlEncoding(String xml, String encoding) {
1960
        return changeXmlEncoding(xml, XmlStringBuilder.Step.TWO_SPACES, encoding);
1✔
1961
    }
1962
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc