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

javadev / underscore-java / #3785

04 Apr 2024 04:41AM UTC coverage: 100.0%. Remained the same
#3785

push

web-flow
Merge fa30c8ad9 into f441cc437

4381 of 4381 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-2024 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({"java:S107", "java:S1119", "java:S2583", "java:S3740", "java:S3776", "java:S4276"})
42
public final class Xml {
43
    private Xml() {}
44

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

79
    static {
80
        XML_UNESCAPE.put(QUOT, "\"");
1✔
81
        XML_UNESCAPE.put("&amp;", "&");
1✔
82
        XML_UNESCAPE.put("&lt;", "<");
1✔
83
        XML_UNESCAPE.put("&gt;", ">");
1✔
84
        XML_UNESCAPE.put("&apos;", "'");
1✔
85
    }
1✔
86

87
    public enum ArrayTrue {
1✔
88
        ADD,
1✔
89
        SKIP
1✔
90
    }
91

92
    public static class XmlStringBuilder {
93
        public enum Step {
1✔
94
            TWO_SPACES(2),
1✔
95
            THREE_SPACES(3),
1✔
96
            FOUR_SPACES(4),
1✔
97
            COMPACT(0),
1✔
98
            TABS(1);
1✔
99
            private final int ident;
100

101
            Step(int ident) {
1✔
102
                this.ident = ident;
1✔
103
            }
1✔
104

105
            public int getIdent() {
106
                return ident;
1✔
107
            }
108
        }
109

110
        protected final StringBuilder builder;
111
        private final Step identStep;
112
        private int ident;
113

114
        public XmlStringBuilder() {
1✔
115
            builder = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\n");
1✔
116
            identStep = Step.TWO_SPACES;
1✔
117
            ident = 2;
1✔
118
        }
1✔
119

120
        public XmlStringBuilder(StringBuilder builder, Step identStep, int ident) {
1✔
121
            this.builder = builder;
1✔
122
            this.identStep = identStep;
1✔
123
            this.ident = ident;
1✔
124
        }
1✔
125

126
        public XmlStringBuilder append(final String string) {
127
            builder.append(string);
1✔
128
            return this;
1✔
129
        }
130

131
        public XmlStringBuilder fillSpaces() {
132
            builder.append(
1✔
133
                    String.valueOf(identStep == Step.TABS ? '\t' : ' ').repeat(Math.max(0, ident)));
1✔
134
            return this;
1✔
135
        }
136

137
        public XmlStringBuilder incIdent() {
138
            ident += identStep.getIdent();
1✔
139
            return this;
1✔
140
        }
141

142
        public XmlStringBuilder decIdent() {
143
            ident -= identStep.getIdent();
1✔
144
            return this;
1✔
145
        }
146

147
        public XmlStringBuilder newLine() {
148
            if (identStep != Step.COMPACT) {
1✔
149
                builder.append("\n");
1✔
150
            }
151
            return this;
1✔
152
        }
153

154
        public int getIdent() {
155
            return ident;
1✔
156
        }
157

158
        public Step getIdentStep() {
159
            return identStep;
1✔
160
        }
161

162
        public String toString() {
163
            return builder.toString() + "\n</root>";
1✔
164
        }
165
    }
166

167
    public static class XmlStringBuilderWithoutRoot extends XmlStringBuilder {
168
        public XmlStringBuilderWithoutRoot(
169
                XmlStringBuilder.Step identStep, String encoding, String standalone) {
170
            super(
1✔
171
                    new StringBuilder(
172
                            "<?xml version=\"1.0\" encoding=\""
173
                                    + XmlValue.escape(encoding).replace("\"", QUOT)
1✔
174
                                    + "\""
175
                                    + standalone
176
                                    + "?>"
177
                                    + (identStep == Step.COMPACT ? "" : "\n")),
1✔
178
                    identStep,
179
                    0);
180
        }
1✔
181

182
        @Override
183
        public String toString() {
184
            return builder.toString();
1✔
185
        }
186
    }
187

188
    public static class XmlStringBuilderWithoutHeader extends XmlStringBuilder {
189
        public XmlStringBuilderWithoutHeader(XmlStringBuilder.Step identStep, int ident) {
190
            super(new StringBuilder(), identStep, ident);
1✔
191
        }
1✔
192

193
        @Override
194
        public String toString() {
195
            return builder.toString();
1✔
196
        }
197
    }
198

199
    public static class XmlStringBuilderText extends XmlStringBuilderWithoutHeader {
200
        public XmlStringBuilderText(XmlStringBuilder.Step identStep, int ident) {
201
            super(identStep, ident);
1✔
202
        }
1✔
203
    }
204

205
    public static class XmlArray {
206
        private XmlArray() {}
207

208
        public static void writeXml(
209
                Collection<?> collection,
210
                String name,
211
                XmlStringBuilder builder,
212
                boolean parentTextFound,
213
                Set<String> namespaces,
214
                boolean addArray,
215
                String arrayTrue) {
216
            if (collection == null) {
1✔
217
                builder.append(NULL);
1✔
218
                return;
1✔
219
            }
220

221
            if (name != null) {
1✔
222
                builder.fillSpaces().append("<").append(XmlValue.escapeName(name, namespaces));
1✔
223
                if (addArray) {
1✔
224
                    builder.append(arrayTrue);
1✔
225
                }
226
                if (collection.isEmpty()) {
1✔
227
                    builder.append(" empty-array=\"true\"");
1✔
228
                }
229
                builder.append(">").incIdent();
1✔
230
                if (!collection.isEmpty()) {
1✔
231
                    builder.newLine();
1✔
232
                }
233
            }
234
            writeXml(collection, builder, name, parentTextFound, namespaces, arrayTrue);
1✔
235
            if (name != null) {
1✔
236
                builder.decIdent();
1✔
237
                if (!collection.isEmpty()) {
1✔
238
                    builder.newLine().fillSpaces();
1✔
239
                }
240
                builder.append("</").append(XmlValue.escapeName(name, namespaces)).append(">");
1✔
241
            }
242
        }
1✔
243

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

303
        public static void writeXml(byte[] array, XmlStringBuilder builder) {
304
            if (array == null) {
1✔
305
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
306
            } else if (array.length == 0) {
1✔
307
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
308
            } else {
309
                for (int i = 0; i < array.length; i++) {
1✔
310
                    builder.fillSpaces().append(ELEMENT);
1✔
311
                    builder.append(String.valueOf(array[i]));
1✔
312
                    builder.append(CLOSED_ELEMENT);
1✔
313
                    if (i != array.length - 1) {
1✔
314
                        builder.newLine();
1✔
315
                    }
316
                }
317
            }
318
        }
1✔
319

320
        public static void writeXml(short[] array, XmlStringBuilder builder) {
321
            if (array == null) {
1✔
322
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
323
            } else if (array.length == 0) {
1✔
324
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
325
            } else {
326
                for (int i = 0; i < array.length; i++) {
1✔
327
                    builder.fillSpaces().append(ELEMENT);
1✔
328
                    builder.append(String.valueOf(array[i]));
1✔
329
                    builder.append(CLOSED_ELEMENT);
1✔
330
                    if (i != array.length - 1) {
1✔
331
                        builder.newLine();
1✔
332
                    }
333
                }
334
            }
335
        }
1✔
336

337
        public static void writeXml(int[] array, XmlStringBuilder builder) {
338
            if (array == null) {
1✔
339
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
340
            } else if (array.length == 0) {
1✔
341
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
342
            } else {
343
                for (int i = 0; i < array.length; i++) {
1✔
344
                    builder.fillSpaces().append(ELEMENT);
1✔
345
                    builder.append(String.valueOf(array[i]));
1✔
346
                    builder.append(CLOSED_ELEMENT);
1✔
347
                    if (i != array.length - 1) {
1✔
348
                        builder.newLine();
1✔
349
                    }
350
                }
351
            }
352
        }
1✔
353

354
        public static void writeXml(long[] array, XmlStringBuilder builder) {
355
            if (array == null) {
1✔
356
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
357
            } else if (array.length == 0) {
1✔
358
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
359
            } else {
360
                for (int i = 0; i < array.length; i++) {
1✔
361
                    builder.fillSpaces().append(ELEMENT);
1✔
362
                    builder.append(String.valueOf(array[i]));
1✔
363
                    builder.append(CLOSED_ELEMENT);
1✔
364
                    if (i != array.length - 1) {
1✔
365
                        builder.newLine();
1✔
366
                    }
367
                }
368
            }
369
        }
1✔
370

371
        public static void writeXml(float[] array, XmlStringBuilder builder) {
372
            if (array == null) {
1✔
373
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
374
            } else if (array.length == 0) {
1✔
375
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
376
            } else {
377
                for (int i = 0; i < array.length; i++) {
1✔
378
                    builder.fillSpaces().append(ELEMENT);
1✔
379
                    builder.append(String.valueOf(array[i]));
1✔
380
                    builder.append(CLOSED_ELEMENT);
1✔
381
                    if (i != array.length - 1) {
1✔
382
                        builder.newLine();
1✔
383
                    }
384
                }
385
            }
386
        }
1✔
387

388
        public static void writeXml(double[] array, XmlStringBuilder builder) {
389
            if (array == null) {
1✔
390
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
391
            } else if (array.length == 0) {
1✔
392
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
393
            } else {
394
                for (int i = 0; i < array.length; i++) {
1✔
395
                    builder.fillSpaces().append(ELEMENT);
1✔
396
                    builder.append(String.valueOf(array[i]));
1✔
397
                    builder.append(CLOSED_ELEMENT);
1✔
398
                    if (i != array.length - 1) {
1✔
399
                        builder.newLine();
1✔
400
                    }
401
                }
402
            }
403
        }
1✔
404

405
        public static void writeXml(boolean[] array, XmlStringBuilder builder) {
406
            if (array == null) {
1✔
407
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
408
            } else if (array.length == 0) {
1✔
409
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
410
            } else {
411
                for (int i = 0; i < array.length; i++) {
1✔
412
                    builder.fillSpaces().append(ELEMENT);
1✔
413
                    builder.append(String.valueOf(array[i]));
1✔
414
                    builder.append(CLOSED_ELEMENT);
1✔
415
                    if (i != array.length - 1) {
1✔
416
                        builder.newLine();
1✔
417
                    }
418
                }
419
            }
420
        }
1✔
421

422
        public static void writeXml(char[] array, XmlStringBuilder builder) {
423
            if (array == null) {
1✔
424
                builder.fillSpaces().append(NULL_ELEMENT);
1✔
425
            } else if (array.length == 0) {
1✔
426
                builder.fillSpaces().append(EMPTY_ELEMENT);
1✔
427
            } else {
428
                for (int i = 0; i < array.length; i++) {
1✔
429
                    builder.fillSpaces().append(ELEMENT);
1✔
430
                    builder.append(String.valueOf(array[i]));
1✔
431
                    builder.append(CLOSED_ELEMENT);
1✔
432
                    if (i != array.length - 1) {
1✔
433
                        builder.newLine();
1✔
434
                    }
435
                }
436
            }
437
        }
1✔
438

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

468
    public static class XmlObject {
469
        private XmlObject() {}
470

471
        @SuppressWarnings("unchecked")
472
        public static void writeXml(
473
                final Map map,
474
                final String name,
475
                final XmlStringBuilder builder,
476
                final boolean parentTextFound,
477
                final Set<String> namespaces,
478
                final boolean addArray,
479
                final String arrayTrue) {
480
            if (map == null) {
1✔
481
                XmlValue.writeXml(NULL, name, builder, false, namespaces, addArray, arrayTrue);
1✔
482
                return;
1✔
483
            }
484

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

534
        @SuppressWarnings("unchecked")
535
        private static void fillNamespacesAndAttrs(
536
                final Map map, final Set<String> namespaces, final Set<String> attrKeys) {
537
            for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
1✔
538
                if (String.valueOf(entry.getKey()).startsWith("-")
1✔
539
                        && !(entry.getValue() instanceof Map)
1✔
540
                        && !(entry.getValue() instanceof List)) {
1✔
541
                    if (String.valueOf(entry.getKey()).startsWith("-xmlns:")) {
1✔
542
                        namespaces.add(String.valueOf(entry.getKey()).substring(7));
1✔
543
                    }
544
                    attrKeys.add(String.valueOf(entry.getKey()));
1✔
545
                }
546
            }
1✔
547
        }
1✔
548

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

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

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

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

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

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

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

730
        private static XmlStringBuilder addCommentValue(
731
                XmlStringBuilder.Step identStep,
732
                int ident,
733
                String value,
734
                boolean parentTextFound,
735
                boolean addNewLine) {
736
            XmlStringBuilder localBuilder = new XmlStringBuilderWithoutHeader(identStep, ident);
1✔
737
            if (!parentTextFound) {
1✔
738
                localBuilder.fillSpaces();
1✔
739
            }
740
            localBuilder.append("<!--").append(value).append("-->");
1✔
741
            if (addNewLine) {
1✔
742
                localBuilder.newLine();
1✔
743
            }
744
            return localBuilder;
1✔
745
        }
746

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

770
        private static XmlStringBuilder addCdataValue(
771
                XmlStringBuilder.Step identStep, int ident, String value, boolean addNewLine) {
772
            XmlStringBuilder localBuilder = new XmlStringBuilderText(identStep, ident);
1✔
773
            localBuilder.append("<![CDATA[").append(value).append("]]>");
1✔
774
            if (addNewLine) {
1✔
775
                localBuilder.newLine();
1✔
776
            }
777
            return localBuilder;
1✔
778
        }
779
    }
780

781
    public static class XmlValue {
782
        private XmlValue() {}
783

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

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

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

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

992
        public static String escape(String s) {
993
            if (s == null) {
1✔
994
                return "";
1✔
995
            }
996
            StringBuilder sb = new StringBuilder();
1✔
997
            escape(s, sb);
1✔
998
            return sb.toString();
1✔
999
        }
1000

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

1052
        public static String unescape(String s) {
1053
            if (s == null) {
1✔
1054
                return "";
1✔
1055
            }
1056
            StringBuilder sb = new StringBuilder();
1✔
1057
            unescape(s, sb);
1✔
1058
            return sb.toString();
1✔
1059
        }
1060

1061
        private static void unescape(String s, StringBuilder sb) {
1062
            final int len = s.length();
1✔
1063
            final StringBuilder localSb = new StringBuilder();
1✔
1064
            int index = 0;
1✔
1065
            while (index < len) {
1✔
1066
                final int skipChars = translate(s, index, localSb);
1✔
1067
                if (skipChars > 0) {
1✔
1068
                    sb.append(localSb);
1✔
1069
                    localSb.setLength(0);
1✔
1070
                    index += skipChars;
1✔
1071
                } else {
1072
                    sb.append(s.charAt(index));
1✔
1073
                    index += 1;
1✔
1074
                }
1075
            }
1✔
1076
        }
1✔
1077

1078
        private static int translate(
1079
                final CharSequence input, final int index, final StringBuilder builder) {
1080
            final int shortest = 4;
1✔
1081
            final int longest = 6;
1✔
1082

1083
            if ('&' == input.charAt(index)) {
1✔
1084
                int max = longest;
1✔
1085
                if (index + longest > input.length()) {
1✔
1086
                    max = input.length() - index;
1✔
1087
                }
1088
                for (int i = max; i >= shortest; i--) {
1✔
1089
                    final CharSequence subSeq = input.subSequence(index, index + i);
1✔
1090
                    final String result = XML_UNESCAPE.get(subSeq.toString());
1✔
1091
                    if (result != null) {
1✔
1092
                        builder.append(result);
1✔
1093
                        return i;
1✔
1094
                    }
1095
                }
1096
            }
1097
            return 0;
1✔
1098
        }
1099

1100
        public static String getMapKey(Object map) {
1101
            return map instanceof Map && !((Map) map).isEmpty()
1✔
1102
                    ? String.valueOf(
1✔
1103
                            ((Map.Entry) ((Map) map).entrySet().iterator().next()).getKey())
1✔
1104
                    : "";
1✔
1105
        }
1106

1107
        public static Object getMapValue(Object map) {
1108
            return map instanceof Map && !((Map) map).isEmpty()
1✔
1109
                    ? ((Map.Entry) ((Map) map).entrySet().iterator().next()).getValue()
1✔
1110
                    : null;
1✔
1111
        }
1112
    }
1113

1114
    public static String toXml(Collection collection, XmlStringBuilder.Step identStep) {
1115
        final XmlStringBuilder builder =
1✔
1116
                new XmlStringBuilderWithoutRoot(identStep, UTF_8.name(), "");
1✔
1117
        writeArray(collection, builder, ARRAY_TRUE);
1✔
1118
        return builder.toString();
1✔
1119
    }
1120

1121
    public static String toXml(Collection collection) {
1122
        return toXml(collection, XmlStringBuilder.Step.TWO_SPACES);
1✔
1123
    }
1124

1125
    public static String toXml(Map map, XmlStringBuilder.Step identStep) {
1126
        return toXml(map, identStep, ROOT, ArrayTrue.ADD);
1✔
1127
    }
1128

1129
    public static String toXml(Map map, XmlStringBuilder.Step identStep, String newRootName) {
1130
        return toXml(map, identStep, newRootName, ArrayTrue.ADD);
1✔
1131
    }
1132

1133
    public static String toXml(
1134
            Map map, XmlStringBuilder.Step identStep, String newRootName, ArrayTrue arrayTrue) {
1135
        final XmlStringBuilder builder;
1136
        final Map localMap;
1137
        if (map != null && map.containsKey(ENCODING)) {
1✔
1138
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1139
            builder =
1✔
1140
                    checkStandalone(String.valueOf(localMap.remove(ENCODING)), identStep, localMap);
1✔
1141
        } else if (map != null && map.containsKey(STANDALONE)) {
1✔
1142
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1143
            builder =
1✔
1144
                    new XmlStringBuilderWithoutRoot(
1145
                            identStep,
1146
                            UTF_8.name(),
1✔
1147
                            " standalone=\""
1148
                                    + (YES.equals(map.get(STANDALONE)) ? YES : "no")
1✔
1149
                                    + "\"");
1150
            localMap.remove(STANDALONE);
1✔
1151
        } else if (map != null && map.containsKey(OMITXMLDECLARATION)) {
1✔
1152
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1153
            builder = new XmlStringBuilderWithoutHeader(identStep, 0);
1✔
1154
            localMap.remove(OMITXMLDECLARATION);
1✔
1155
        } else {
1156
            builder = new XmlStringBuilderWithoutRoot(identStep, UTF_8.name(), "");
1✔
1157
            localMap = map;
1✔
1158
        }
1159
        checkLocalMap(builder, localMap, newRootName, arrayTrue == ArrayTrue.ADD ? ARRAY_TRUE : "");
1✔
1160
        return builder.toString();
1✔
1161
    }
1162

1163
    private static void checkLocalMap(
1164
            final XmlStringBuilder builder,
1165
            final Map localMap,
1166
            final String newRootName,
1167
            final String arrayTrue) {
1168
        final Map localMap2;
1169
        if (localMap != null && localMap.containsKey(DOCTYPE_TEXT)) {
1✔
1170
            localMap2 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1171
            localMap2.remove(DOCTYPE_TEXT);
1✔
1172
            builder.append(DOCTYPE_HEADER)
1✔
1173
                    .append(String.valueOf(localMap.get(DOCTYPE_TEXT)))
1✔
1174
                    .append(">")
1✔
1175
                    .newLine();
1✔
1176
        } else {
1177
            localMap2 = localMap;
1✔
1178
        }
1179
        if (localMap2 == null
1✔
1180
                || localMap2.size() != 1
1✔
1181
                || XmlValue.getMapKey(localMap2).startsWith("-")
1✔
1182
                || XmlValue.getMapValue(localMap2) instanceof List) {
1✔
1183
            if (ROOT.equals(XmlValue.getMapKey(localMap2))) {
1✔
1184
                writeArray((List) XmlValue.getMapValue(localMap2), builder, arrayTrue);
1✔
1185
            } else {
1186
                XmlObject.writeXml(
1✔
1187
                        localMap2,
1188
                        getRootName(localMap2, newRootName),
1✔
1189
                        builder,
1190
                        false,
1191
                        new LinkedHashSet<>(),
1192
                        false,
1193
                        arrayTrue);
1194
            }
1195
        } else {
1196
            XmlObject.writeXml(
1✔
1197
                    localMap2,
1198
                    getRootName(localMap2, newRootName),
1✔
1199
                    builder,
1200
                    false,
1201
                    new LinkedHashSet<>(),
1202
                    false,
1203
                    arrayTrue);
1204
        }
1205
    }
1✔
1206

1207
    private static void writeArray(
1208
            final Collection collection, final XmlStringBuilder builder, final String arrayTrue) {
1209
        builder.append("<root");
1✔
1210
        if (collection != null && collection.isEmpty()) {
1✔
1211
            builder.append(" empty-array=\"true\"");
1✔
1212
        }
1213
        builder.append(">").incIdent();
1✔
1214
        if (collection != null && !collection.isEmpty()) {
1✔
1215
            builder.newLine();
1✔
1216
        }
1217
        XmlArray.writeXml(
1✔
1218
                collection, null, builder, false, new LinkedHashSet<>(), false, arrayTrue);
1219
        if (collection != null && !collection.isEmpty()) {
1✔
1220
            builder.newLine();
1✔
1221
        }
1222
        builder.append("</root>");
1✔
1223
    }
1✔
1224

1225
    private static XmlStringBuilder checkStandalone(
1226
            String encoding, XmlStringBuilder.Step identStep, final Map localMap) {
1227
        final XmlStringBuilder builder;
1228
        if (localMap.containsKey(STANDALONE)) {
1✔
1229
            builder =
1✔
1230
                    new XmlStringBuilderWithoutRoot(
1231
                            identStep,
1232
                            encoding,
1233
                            " standalone=\""
1234
                                    + (YES.equals(localMap.get(STANDALONE)) ? YES : "no")
1✔
1235
                                    + "\"");
1236
            localMap.remove(STANDALONE);
1✔
1237
        } else {
1238
            builder = new XmlStringBuilderWithoutRoot(identStep, encoding, "");
1✔
1239
        }
1240
        return builder;
1✔
1241
    }
1242

1243
    @SuppressWarnings("unchecked")
1244
    private static String getRootName(final Map localMap, final String newRootName) {
1245
        int foundAttrs = 0;
1✔
1246
        int foundElements = 0;
1✔
1247
        int foundListElements = 0;
1✔
1248
        if (localMap != null) {
1✔
1249
            for (Map.Entry entry : (Set<Map.Entry>) localMap.entrySet()) {
1✔
1250
                if (String.valueOf(entry.getKey()).startsWith("-")) {
1✔
1251
                    foundAttrs += 1;
1✔
1252
                } else if (!String.valueOf(entry.getKey()).startsWith(COMMENT)
1✔
1253
                        && !String.valueOf(entry.getKey()).startsWith(CDATA)
1✔
1254
                        && !String.valueOf(entry.getKey()).startsWith("?")) {
1✔
1255
                    if (entry.getValue() instanceof List && ((List) entry.getValue()).size() > 1) {
1✔
1256
                        foundListElements += 1;
1✔
1257
                    }
1258
                    foundElements += 1;
1✔
1259
                }
1260
            }
1✔
1261
        }
1262
        return foundAttrs == 0 && foundElements == 1 && foundListElements == 0 ? null : newRootName;
1✔
1263
    }
1264

1265
    public static String toXml(Map map) {
1266
        return toXml(map, XmlStringBuilder.Step.TWO_SPACES, ROOT);
1✔
1267
    }
1268

1269
    @SuppressWarnings("unchecked")
1270
    private static Object getValue(final String name, final Object value, final FromType fromType) {
1271
        final Object localValue;
1272
        if (value instanceof Map && ((Map<String, Object>) value).entrySet().size() == 1) {
1✔
1273
            final Map.Entry<String, Object> entry =
1✔
1274
                    ((Map<String, Object>) value).entrySet().iterator().next();
1✔
1275
            if (TEXT.equals(entry.getKey())
1✔
1276
                    || (fromType == FromType.FOR_CONVERT && ELEMENT_TEXT.equals(entry.getKey()))) {
1✔
1277
                localValue = entry.getValue();
1✔
1278
            } else {
1279
                localValue = value;
1✔
1280
            }
1281
        } else {
1✔
1282
            localValue = value;
1✔
1283
        }
1284
        return localValue instanceof String && name.startsWith("-")
1✔
1285
                ? XmlValue.unescape((String) localValue)
1✔
1286
                : localValue;
1✔
1287
    }
1288

1289
    public static Object stringToNumber(String number) {
1290
        final Object localValue;
1291
        if (number.contains(".") || number.contains("e") || number.contains("E")) {
1✔
1292
            if (number.length() > 9
1✔
1293
                    || (number.contains(".") && number.length() - number.lastIndexOf('.') > 2)
1✔
1294
                            && number.charAt(number.length() - 1) == '0') {
1✔
1295
                localValue = new java.math.BigDecimal(number);
1✔
1296
            } else {
1297
                localValue = Double.valueOf(number);
1✔
1298
            }
1299
        } else {
1300
            if (number.length() > 19) {
1✔
1301
                localValue = new java.math.BigInteger(number);
1✔
1302
            } else {
1303
                localValue = Long.valueOf(number);
1✔
1304
            }
1305
        }
1306
        return localValue;
1✔
1307
    }
1308

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

1380
    @SuppressWarnings("unchecked")
1381
    private static Object checkNumberAndBoolean(final Map<String, Object> map, final String name) {
1382
        final Map<String, Object> localMap;
1383
        if (map.containsKey(NUMBER) && TRUE.equals(map.get(NUMBER)) && map.containsKey(TEXT)) {
1✔
1384
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1385
            localMap.remove(NUMBER);
1✔
1386
            localMap.put(TEXT, stringToNumber(String.valueOf(localMap.get(TEXT))));
1✔
1387
        } else {
1388
            localMap = map;
1✔
1389
        }
1390
        final Map<String, Object> localMap2;
1391
        if (map.containsKey(BOOLEAN) && TRUE.equals(map.get(BOOLEAN)) && map.containsKey(TEXT)) {
1✔
1392
            localMap2 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1393
            localMap2.remove(BOOLEAN);
1✔
1394
            localMap2.put(TEXT, Boolean.valueOf(String.valueOf(localMap.get(TEXT))));
1✔
1395
        } else {
1396
            localMap2 = localMap;
1✔
1397
        }
1398
        return checkArray(localMap2, name);
1✔
1399
    }
1400

1401
    @SuppressWarnings("unchecked")
1402
    private static Object checkArray(final Map<String, Object> map, final String name) {
1403
        final Map<String, Object> localMap = checkNullAndString(map);
1✔
1404
        final Object object;
1405
        if (map.containsKey(ARRAY) && TRUE.equals(map.get(ARRAY))) {
1✔
1406
            final Map<String, Object> localMap4 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1407
            localMap4.remove(ARRAY);
1✔
1408
            localMap4.remove(SELF_CLOSING);
1✔
1409
            object =
1410
                    name.equals(XmlValue.getMapKey(localMap4))
1✔
1411
                            ? new ArrayList<>(
1✔
1412
                                    Collections.singletonList(
1✔
1413
                                            getValue(
1✔
1414
                                                    name,
1415
                                                    XmlValue.getMapValue(localMap4),
1✔
1416
                                                    FromType.FOR_CONVERT)))
1417
                            : new ArrayList<>(
1✔
1418
                                    Collections.singletonList(
1✔
1419
                                            getValue(name, localMap4, FromType.FOR_CONVERT)));
1✔
1420
        } else {
1✔
1421
            object = localMap;
1✔
1422
        }
1423
        final Object object2;
1424
        if (map.containsKey(EMPTY_ARRAY) && TRUE.equals(map.get(EMPTY_ARRAY))) {
1✔
1425
            final Map<String, Object> localMap4 = (Map) ((LinkedHashMap) map).clone();
1✔
1426
            localMap4.remove(EMPTY_ARRAY);
1✔
1427
            if (localMap4.containsKey(ARRAY)
1✔
1428
                    && TRUE.equals(localMap4.get(ARRAY))
1✔
1429
                    && localMap4.size() == 1) {
1✔
1430
                object2 = new ArrayList<>();
1✔
1431
                ((List) object2).add(new ArrayList<>());
1✔
1432
            } else {
1433
                object2 = localMap4.isEmpty() ? new ArrayList<>() : localMap4;
1✔
1434
            }
1435
        } else {
1✔
1436
            object2 = object;
1✔
1437
        }
1438
        return object2;
1✔
1439
    }
1440

1441
    @SuppressWarnings("unchecked")
1442
    private static Map<String, Object> checkNullAndString(final Map<String, Object> map) {
1443
        final Map<String, Object> localMap;
1444
        if (map.containsKey(NULL_ATTR) && TRUE.equals(map.get(NULL_ATTR))) {
1✔
1445
            localMap = (Map) ((LinkedHashMap) map).clone();
1✔
1446
            localMap.remove(NULL_ATTR);
1✔
1447
            if (!map.containsKey(TEXT)) {
1✔
1448
                localMap.put(TEXT, null);
1✔
1449
            }
1450
        } else {
1451
            localMap = map;
1✔
1452
        }
1453
        final Map<String, Object> localMap2;
1454
        if (map.containsKey(STRING) && TRUE.equals(map.get(STRING))) {
1✔
1455
            localMap2 = (Map) ((LinkedHashMap) localMap).clone();
1✔
1456
            localMap2.remove(STRING);
1✔
1457
            if (!map.containsKey(TEXT)) {
1✔
1458
                localMap2.put(TEXT, "");
1✔
1459
            }
1460
        } else {
1461
            localMap2 = localMap;
1✔
1462
        }
1463
        return localMap2;
1✔
1464
    }
1465

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

1517
    static Map<String, String> parseAttributes(final String source) {
1518
        final Map<String, String> result = new LinkedHashMap<>();
1✔
1519
        final StringBuilder key = new StringBuilder();
1✔
1520
        final StringBuilder value = new StringBuilder();
1✔
1521
        boolean quoteFound = false;
1✔
1522
        boolean equalFound = false;
1✔
1523
        for (int index = 0; index < source.length(); index += 1) {
1✔
1524
            if (source.charAt(index) == '=') {
1✔
1525
                equalFound = !equalFound;
1✔
1526
                continue;
1✔
1527
            }
1528
            if (source.charAt(index) == '"') {
1✔
1529
                if (quoteFound && equalFound) {
1✔
1530
                    result.put(key.toString(), value.toString());
1✔
1531
                    key.setLength(0);
1✔
1532
                    value.setLength(0);
1✔
1533
                    equalFound = false;
1✔
1534
                }
1535
                quoteFound = !quoteFound;
1✔
1536
            } else if (quoteFound || SKIPPED_CHARS.contains(source.charAt(index))) {
1✔
1537
                if (quoteFound) {
1✔
1538
                    value.append(source.charAt(index));
1✔
1539
                }
1540
            } else {
1541
                key.append(source.charAt(index));
1✔
1542
            }
1543
        }
1544
        return result;
1✔
1545
    }
1546

1547
    static String getAttributes(final int sourceIndex, final String source) {
1548
        boolean scanQuote = false;
1✔
1549
        for (int index = sourceIndex; index < source.length(); index += 1) {
1✔
1550
            if (source.charAt(index) == '"') {
1✔
1551
                scanQuote = !scanQuote;
1✔
1552
                continue;
1✔
1553
            }
1554
            if (!scanQuote && source.charAt(index) == '>') {
1✔
1555
                return source.substring(sourceIndex, index);
1✔
1556
            }
1557
        }
1558
        return "";
1✔
1559
    }
1560

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

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

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

1689
    public static Object fromXml(final String xml) {
1690
        return fromXml(xml, FromType.FOR_CONVERT);
1✔
1691
    }
1692

1693
    public enum FromType {
1✔
1694
        FOR_CONVERT,
1✔
1695
        FOR_FORMAT
1✔
1696
    }
1697

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

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

1754
    private static Map<String, String> getHeaderAttributes(final String xml) {
1755
        final Map<String, String> result = new LinkedHashMap<>();
1✔
1756
        if (xml.startsWith(XML_HEADER)) {
1✔
1757
            final String xmlLocal =
1✔
1758
                    xml.substring(
1✔
1759
                            XML_HEADER.length(),
1✔
1760
                            Math.max(XML_HEADER.length(), xml.indexOf("?>", XML_HEADER.length())));
1✔
1761
            final Map<String, String> attributes = parseAttributes(xmlLocal);
1✔
1762
            result.putAll(attributes);
1✔
1763
        }
1764
        return result;
1✔
1765
    }
1766

1767
    static String getDoctypeValue(final String xml) {
1768
        int startIndex = xml.indexOf(DOCTYPE_HEADER) + DOCTYPE_HEADER.length();
1✔
1769
        char charToFind = '>';
1✔
1770
        int endIndexPlus = 0;
1✔
1771
        for (int endIndex = startIndex; endIndex < xml.length(); endIndex += 1) {
1✔
1772
            if (xml.charAt(endIndex) == '[') {
1✔
1773
                charToFind = ']';
1✔
1774
                endIndexPlus = 1;
1✔
1775
                continue;
1✔
1776
            }
1777
            if (xml.charAt(endIndex) == charToFind) {
1✔
1778
                return xml.substring(startIndex, endIndex + endIndexPlus);
1✔
1779
            }
1780
        }
1781
        return "";
1✔
1782
    }
1783

1784
    private static class MyEntityResolver implements org.xml.sax.EntityResolver {
1785
        public org.xml.sax.InputSource resolveEntity(String publicId, String systemId) {
1786
            return new org.xml.sax.InputSource(new java.io.StringReader(""));
1✔
1787
        }
1788
    }
1789

1790
    protected static class Document {
1791
        private Document() {}
1792

1793
        public static org.w3c.dom.Document createDocument(final String xml)
1794
                throws java.io.IOException, javax.xml.parsers.ParserConfigurationException,
1795
                        org.xml.sax.SAXException {
1796
            final javax.xml.parsers.DocumentBuilderFactory factory =
1797
                    javax.xml.parsers.DocumentBuilderFactory.newInstance();
1798
            factory.setNamespaceAware(true);
1799
            try {
1800
                factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
1801
            } catch (Exception ignored) {
1802
                // ignored
1803
            }
1804
            final javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
1805
            builder.setErrorHandler(new org.xml.sax.helpers.DefaultHandler());
1806
            builder.setEntityResolver(new MyEntityResolver());
1807
            return builder.parse(new org.xml.sax.InputSource(new java.io.StringReader(xml)));
1808
        }
1809

1810
        private static org.w3c.dom.Document createDocument() {
1811
            try {
1812
                final javax.xml.parsers.DocumentBuilderFactory factory =
1813
                        javax.xml.parsers.DocumentBuilderFactory.newInstance();
1814
                factory.setNamespaceAware(true);
1815
                setupFactory(factory);
1816
                final javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
1817
                return builder.newDocument();
1818
            } catch (javax.xml.parsers.ParserConfigurationException ex) {
1819
                throw new IllegalArgumentException(ex);
1820
            }
1821
        }
1822

1823
        private static void setupFactory(javax.xml.parsers.DocumentBuilderFactory factory) {
1824
            try {
1825
                factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
1826
            } catch (Exception ignored) {
1827
                // ignored
1828
            }
1829
        }
1830
    }
1831

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

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

1882
    public static Object fromXmlWithoutNamespaces(final String xml) {
1883
        return fromXmlWithElementMapper(
1✔
1884
                xml,
1885
                (object, namespaces) -> {
1886
                    final String localString = String.valueOf(object);
1✔
1887
                    final String result;
1888
                    String substring = localString.substring(
1✔
1889
                            Math.max(0, localString.indexOf(':') + 1));
1✔
1890
                    if (localString.startsWith("-")
1✔
1891
                            && namespaces.contains(
1✔
1892
                                    localString.substring(
1✔
1893
                                            1, Math.max(1, localString.indexOf(':'))))) {
1✔
1894
                        result =
1✔
1895
                                "-"
1896
                                        + substring;
1897
                    } else if (namespaces.contains(
1✔
1898
                            localString.substring(0, Math.max(0, localString.indexOf(':'))))) {
1✔
1899
                        result = substring;
1✔
1900
                    } else {
1901
                        result = String.valueOf(object);
1✔
1902
                    }
1903
                    return result;
1✔
1904
                });
1905
    }
1906

1907
    public static Object fromXmlWithoutAttributes(final String xml) {
1908
        return fromXmlWithElementMapper(
1✔
1909
                xml,
1910
                (object, namespaces) ->
1911
                        String.valueOf(object).startsWith("-") ? null : String.valueOf(object));
1✔
1912
    }
1913

1914
    public static Object fromXmlWithoutNamespacesAndAttributes(final String xml) {
1915
        return fromXmlWithElementMapper(
1✔
1916
                xml,
1917
                (object, namespaces) -> {
1918
                    final String localString = String.valueOf(object);
1✔
1919
                    final String result;
1920
                    if (localString.startsWith("-")) {
1✔
1921
                        result = null;
1✔
1922
                    } else if (namespaces.contains(
1✔
1923
                            localString.substring(0, Math.max(0, localString.indexOf(':'))))) {
1✔
1924
                        result = localString.substring(Math.max(0, localString.indexOf(':') + 1));
1✔
1925
                    } else {
1926
                        result = String.valueOf(object);
1✔
1927
                    }
1928
                    return result;
1✔
1929
                });
1930
    }
1931

1932
    public static String formatXml(String xml, XmlStringBuilder.Step identStep) {
1933
        Object result = fromXml(xml, FromType.FOR_FORMAT);
1✔
1934
        return toXml((Map) result, identStep, ROOT);
1✔
1935
    }
1936

1937
    public static String formatXml(String xml) {
1938
        return formatXml(xml, XmlStringBuilder.Step.TWO_SPACES);
1✔
1939
    }
1940

1941
    @SuppressWarnings("unchecked")
1942
    public static String changeXmlEncoding(
1943
            String xml, XmlStringBuilder.Step identStep, String encoding) {
1944
        Object result = fromXml(xml, FromType.FOR_FORMAT);
1✔
1945
        if (result instanceof Map) {
1✔
1946
            ((Map) result).put(ENCODING, encoding);
1✔
1947
            return toXml((Map) result, identStep, ROOT);
1✔
1948
        }
1949
        return xml;
1✔
1950
    }
1951

1952
    public static String changeXmlEncoding(String xml, String encoding) {
1953
        return changeXmlEncoding(xml, XmlStringBuilder.Step.TWO_SPACES, encoding);
1✔
1954
    }
1955
}
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