1 package com.github.davidmoten.aws.lw.client.xml.builder;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.stream.Collectors;
8
9 import com.github.davidmoten.aws.lw.client.internal.util.Preconditions;
10
11 public final class Xml {
12
13 private final String name;
14 private final Xml parent;
15 private Map<String, String> attributes = new HashMap<>();
16 private List<Xml> children = new ArrayList<>();
17 private String content;
18 private boolean prelude = true;
19
20 private Xml(String name) {
21 this(name, null);
22 }
23
24 private Xml(String name, Xml parent) {
25 checkPresent(name, "name");
26 this.name = name;
27 this.parent = parent;
28 }
29
30 public static Xml create(String name) {
31 return new Xml(name);
32 }
33
34 public Xml excludePrelude() {
35 Xml xml = this;
36 while (xml.parent != null) {
37 xml = xml.parent;
38 }
39 xml.prelude = false;
40 return this;
41 }
42
43 public Xml element(String name) {
44 checkPresent(name, "name");
45 Preconditions.checkArgument(content == null,
46 "content cannot be already specified if starting a child element");
47 Xml xml = new Xml(name, this);
48 this.children.add(xml);
49 return xml;
50 }
51
52 public Xml e(String name) {
53 return element(name);
54 }
55
56 public Xml attribute(String name, String value) {
57 checkPresent(name, "name");
58 Preconditions.checkNotNull(value);
59 this.attributes.put(name, value);
60 return this;
61 }
62
63 public Xml a(String name, String value) {
64 return attribute(name, value);
65 }
66
67 public Xml content(String content) {
68 Preconditions.checkArgument(children.isEmpty());
69 this.content = content;
70 return this;
71 }
72
73 public Xml up() {
74 return parent;
75 }
76
77 private static void checkPresent(String s, String name) {
78 if (s == null || s.trim().isEmpty()) {
79 throw new IllegalArgumentException(name + " must be non-null and non-blank");
80 }
81 }
82
83 private String toString(String indent) {
84 StringBuilder b = new StringBuilder();
85 if (indent.length() == 0 && prelude) {
86 b.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
87 }
88
89 String atts = attributes.entrySet().stream().map(
90 entry -> " " + entry.getKey() + "=\"" + encodeXml(entry.getValue(), true) + "\"")
91 .collect(Collectors.joining());
92 b.append(String.format("%s<%s%s>", indent, name, atts));
93 if (content != null) {
94 b.append(encodeXml(content, false));
95 b.append(String.format("</%s>", name));
96 if (parent != null) {
97 b.append("\n");
98 }
99 } else {
100 b.append("\n");
101 for (Xml xml : children) {
102 b.append(xml.toString(indent + " "));
103 }
104 b.append(String.format("%s</%s>", indent, name));
105 if (parent != null) {
106 b.append("\n");
107 }
108 }
109 return b.toString();
110 }
111
112 public String toString() {
113 Xml xml = this;
114 while (xml.parent != null) {
115 xml = xml.parent;
116 }
117 return xml.toString("");
118 }
119
120 private static final Map<Integer, String> CONTENT_CHARACTER_MAP = createContentCharacterMap();
121 private static final Map<Integer, String> ATTRIBUTE_CHARACTER_MAP = createAttributeCharacterMap();
122
123 private static Map<Integer, String> createContentCharacterMap() {
124 Map<Integer, String> m = new HashMap<>();
125 m.put((int) '&', "&");
126 m.put((int) '>', ">");
127 m.put((int) '<', "<");
128 return m;
129 }
130
131 private static Map<Integer, String> createAttributeCharacterMap() {
132 Map<Integer, String> m = new HashMap<>();
133 m.put((int) '\'', "'");
134 m.put((int) '\"', """);
135 return m;
136 }
137
138 private static String encodeXml(CharSequence s, boolean isAttribute) {
139 StringBuilder b = new StringBuilder();
140 int len = s.length();
141 for (int i = 0; i < len; i++) {
142 int c = s.charAt(i);
143 if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
144 c = ((c - 0xd7c0) << 10) | (s.charAt(++i) & 0x3ff);
145 }
146 if (c < 0x80) {
147 if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) {
148
149 b.append("�");
150 } else {
151 String r = CONTENT_CHARACTER_MAP.get(c);
152 if (r != null) {
153 b.append(r);
154 } else if (isAttribute) {
155 String r2 = ATTRIBUTE_CHARACTER_MAP.get(c);
156 if (r2 != null) {
157 b.append(r2);
158 } else {
159 b.append((char) c);
160 }
161 } else {
162 b.append((char) c);
163 }
164
165 }
166 } else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) {
167
168 b.append("�");
169 } else {
170 b.append("&#x");
171 b.append(Integer.toHexString(c));
172 b.append(';');
173 }
174 }
175 return b.toString();
176 }
177
178 }