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