1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.maven.plugins.i18n;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.nio.charset.StandardCharsets;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Properties;
30 import java.util.SortedMap;
31 import java.util.TreeMap;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.lang3.Strings;
35 import org.jdom2.Document;
36 import org.jdom2.Element;
37 import org.jdom2.Namespace;
38 import org.jdom2.output.Format;
39 import org.jdom2.output.XMLOutputter;
40
41 import jakarta.json.Json;
42 import jakarta.json.JsonObject;
43 import jakarta.json.JsonObjectBuilder;
44
45
46
47
48 class SlingI18nMap {
49
50 private static final String JCR_LANGUAGE = "language";
51 private static final List<String> JCR_MIX_LANGUAGE = Collections.singletonList("mix:language");
52 private static final String JCR_MIXIN_TYPES = "mixinTypes";
53 private static final String JCR_NODETYPE_FOLDER = "nt:folder";
54 private static final String JCR_PRIMARY_TYPE = "primaryType";
55
56 private static final String SLING_KEY = "key";
57 private static final String SLING_MESSAGE = "message";
58 private static final List<String> SLING_MESSAGE_MIXIN_TYPE = Collections.singletonList("sling:Message");
59
60 private static final Namespace NAMESPACE_SLING = Namespace.getNamespace("sling", "http://sling.apache.org/jcr/sling/1.0");
61 private static final Namespace NAMESPACE_JCR = Namespace.getNamespace("jcr", "http://www.jcp.org/jcr/1.0");
62 private static final Namespace NAMESPACE_MIX = Namespace.getNamespace("mix", "http://www.jcp.org/jcr/mix/1.0");
63 private static final Namespace NAMESPACE_NT = Namespace.getNamespace("nt", "http://www.jcp.org/jcr/nt/1.0");
64
65 private final String languageKey;
66 private final SortedMap<String, String> properties;
67
68
69
70
71 SlingI18nMap(String languageKey, Map<String, String> properties) {
72 this.languageKey = languageKey;
73 this.properties = new TreeMap<>(properties);
74 }
75
76
77
78
79
80 public String getI18nJsonString() {
81 return JsonUtil.toString(buildI18nJson());
82 }
83
84
85
86
87
88 public String getI18nJsonPropertiesString() {
89 return JsonUtil.toString(buildI18nJsonProperties());
90 }
91
92 private JsonObject buildI18nJson() {
93
94
95 JsonObjectBuilder jsonDocument = getMixLanguageJsonDocument();
96
97
98 for (Entry<String, String> entry : properties.entrySet()) {
99 String key = entry.getKey();
100 String escapedKey = validName(key);
101 JsonObject value = getJsonI18nValue(key, entry.getValue(), !Strings.CS.equals(key, escapedKey));
102
103 jsonDocument.add(escapedKey, value);
104 }
105
106
107 return jsonDocument.build();
108 }
109
110 private JsonObject buildI18nJsonProperties() {
111 JsonObjectBuilder jsonDocument = Json.createObjectBuilder();
112
113
114 for (Entry<String, String> entry : properties.entrySet()) {
115 String key = entry.getKey();
116 String escapedKey = validName(key);
117 jsonDocument.add(escapedKey, entry.getValue());
118 }
119
120
121 return jsonDocument.build();
122 }
123
124 private JsonObjectBuilder getMixLanguageJsonDocument() {
125 JsonObjectBuilder root = Json.createObjectBuilder();
126
127
128 root.add("jcr:" + JCR_PRIMARY_TYPE, JCR_NODETYPE_FOLDER);
129 root.add("jcr:" + JCR_MIXIN_TYPES, Json.createArrayBuilder(JCR_MIX_LANGUAGE).build());
130
131
132 root.add("jcr:" + JCR_LANGUAGE, languageKey);
133
134 return root;
135 }
136
137 private JsonObject getJsonI18nValue(String key, String value, boolean generatedKeyProperty) {
138 JsonObjectBuilder valueNode = Json.createObjectBuilder();
139
140
141 valueNode.add("jcr:" + JCR_PRIMARY_TYPE, JCR_NODETYPE_FOLDER);
142 valueNode.add("jcr:" + JCR_MIXIN_TYPES, Json.createArrayBuilder(SLING_MESSAGE_MIXIN_TYPE).build());
143
144
145 if (generatedKeyProperty) {
146 valueNode.add("sling:" + SLING_KEY, key);
147 }
148
149
150 valueNode.add("sling:" + SLING_MESSAGE, value);
151
152 return valueNode.build();
153 }
154
155
156
157
158
159 public String getI18nXmlString() {
160 Format format = Format.getPrettyFormat();
161 XMLOutputter outputter = new XMLOutputter(format);
162 return outputter.outputString(buildI18nXml());
163 }
164
165 private Document buildI18nXml() {
166
167
168 Document xmlDocument = getMixLanguageXmlDocument();
169
170
171 for (Entry<String, String> entry : properties.entrySet()) {
172 String key = entry.getKey();
173 String escapedKey = validName(key);
174 Element value = getXmlI18nValue(escapedKey, key, entry.getValue(), !Strings.CS.equals(key, escapedKey));
175
176 xmlDocument.getRootElement().addContent(value);
177 }
178
179
180 return xmlDocument;
181 }
182
183 private Document getMixLanguageXmlDocument() {
184 Document doc = new Document();
185 Element root = new Element("root", NAMESPACE_JCR);
186 root.addNamespaceDeclaration(NAMESPACE_JCR);
187 root.addNamespaceDeclaration(NAMESPACE_MIX);
188 root.addNamespaceDeclaration(NAMESPACE_NT);
189 root.addNamespaceDeclaration(NAMESPACE_SLING);
190 doc.setRootElement(root);
191
192
193 root.setAttribute(JCR_PRIMARY_TYPE, JCR_NODETYPE_FOLDER, NAMESPACE_JCR);
194 root.setAttribute(JCR_MIXIN_TYPES, "[" + StringUtils.join(JCR_MIX_LANGUAGE, ",") + "]", NAMESPACE_JCR);
195
196
197 root.setAttribute(JCR_LANGUAGE, languageKey, NAMESPACE_JCR);
198
199 return doc;
200 }
201
202 private Element getXmlI18nValue(String escapedKey, String key, String value, boolean generatedKeyProperty) {
203 Element valueNode = new Element(escapedKey);
204
205
206 valueNode.setAttribute(JCR_PRIMARY_TYPE, JCR_NODETYPE_FOLDER, NAMESPACE_JCR);
207 valueNode.setAttribute(JCR_MIXIN_TYPES, "[" + StringUtils.join(SLING_MESSAGE_MIXIN_TYPE, ",") + "]", NAMESPACE_JCR);
208
209
210 if (generatedKeyProperty) {
211 valueNode.setAttribute(SLING_KEY, key, NAMESPACE_SLING);
212 }
213
214
215 valueNode.setAttribute(SLING_MESSAGE, value, NAMESPACE_SLING);
216
217 return valueNode;
218 }
219
220
221
222
223
224
225 private static String validName(String value) {
226
227
228 String text = value;
229 text = Strings.CS.replace(text, "ä", "ae");
230 text = Strings.CS.replace(text, "ö", "oe");
231 text = Strings.CS.replace(text, "ü", "ue");
232 text = Strings.CS.replace(text, "ß", "ss");
233
234
235 StringBuilder sb = new StringBuilder(text);
236 for (int i = 0; i < sb.length(); i++) {
237 char ch = sb.charAt(i);
238 if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')
239 || (ch == '_') || (ch == '.'))) {
240 ch = '-';
241 sb.setCharAt(i, ch);
242 }
243 }
244 return sb.toString();
245 }
246
247
248
249
250
251 public String getI18nPropertiesString() throws IOException {
252
253 Properties i18nProps = new Properties();
254
255
256 for (Entry<String, String> entry : properties.entrySet()) {
257 String key = entry.getKey();
258 String escapedKey = validName(key);
259 i18nProps.put(escapedKey, entry.getValue());
260 }
261
262 try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
263 i18nProps.store(outStream, null);
264
265 return outStream.toString(StandardCharsets.ISO_8859_1.name());
266 }
267 }
268
269 }