Skip to content

Commit 8e48097

Browse files
authored
Merge pull request #69 from JCEmmons/issue68
Implement support for globalizejs JSON resources. Fixes #68
2 parents 0528e99 + ea31f4c commit 8e48097

10 files changed

Lines changed: 316 additions & 20 deletions

File tree

gp-cli/src/main/java/com/ibm/g11n/pipeline/tools/cli/ImportCmd.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2015
2+
* Copyright IBM Corp. 2015, 2017
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -69,6 +69,12 @@ protected void _execute() {
6969
File f = new File(fileName);
7070
try (FileInputStream fis = new FileInputStream(f)) {
7171
Bundle bundle = filter.parse(fis);
72+
if (!bundle.getLanguage().isEmpty() &&
73+
!bundle.getLanguage().equals(languageId)) {
74+
throw new RuntimeException("Language ID \"" + bundle.getLanguage() +
75+
"\" in the upload file does not match the specified language ID \"" +
76+
languageId + "\"");
77+
}
7278
resEntries = new HashMap<>(bundle.getResourceStrings().size());
7379
for (ResourceString resString : bundle.getResourceStrings()) {
7480
NewResourceEntryData resEntryData = new NewResourceEntryData(resString.getValue());

gp-res-filter/src/main/java/com/ibm/g11n/pipeline/resfilter/Bundle.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2016
2+
* Copyright IBM Corp. 2016, 2017
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323

2424
public final class Bundle {
25+
private String language = "";
2526
private List<String> notes;
2627
private Collection<ResourceString> resStrings;
2728

@@ -80,4 +81,12 @@ public List<String> getNotes() {
8081
}
8182
return Collections.unmodifiableList(notes);
8283
}
84+
85+
public String getLanguage() {
86+
return language;
87+
}
88+
89+
public void setLanguage(String language) {
90+
this.language = language;
91+
}
8392
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright IBM Corp. 2017
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.ibm.g11n.pipeline.resfilter;
17+
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.io.InputStreamReader;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.Map;
23+
import java.util.Set;
24+
25+
import com.google.gson.JsonArray;
26+
import com.google.gson.JsonElement;
27+
import com.google.gson.JsonObject;
28+
import com.google.gson.JsonParseException;
29+
import com.google.gson.JsonParser;
30+
31+
/**
32+
* globalizejs resource filter implementation.
33+
*
34+
* @author John Emmons
35+
*/
36+
public class GlobalizeJsResource extends JsonResource {
37+
38+
@Override
39+
public Bundle parse(InputStream inStream) throws IOException {
40+
Bundle bundle = new Bundle();
41+
try (InputStreamReader reader = new InputStreamReader(new BomInputStream(inStream), StandardCharsets.UTF_8)) {
42+
JsonElement root = new JsonParser().parse(reader);
43+
if (!root.isJsonObject()) {
44+
throw new IllegalResourceFormatException("The root JSON element is not a JSON object.");
45+
}
46+
JsonObject root_obj = root.getAsJsonObject();
47+
Set<Map.Entry<String, JsonElement>> root_elements = root_obj.entrySet();
48+
if (root_elements.size() != 1) {
49+
throw new IllegalResourceFormatException(
50+
"Only one top level language tag element is allowed per file.");
51+
}
52+
Map.Entry<String, JsonElement> top_level = root_elements.iterator().next();
53+
String language = top_level.getKey();
54+
// We just hang on to the language tag as part of the bundle.
55+
// When doing an import, we can validate that the language tag matches what they
56+
// say they are importing.
57+
bundle.setLanguage(language);
58+
JsonElement language_obj = top_level.getValue();
59+
if (!language_obj.isJsonObject()) {
60+
throw new IllegalResourceFormatException("The top level language element is not a JSON object.");
61+
}
62+
addBundleStrings(language_obj.getAsJsonObject(), "", bundle, 0);
63+
} catch (JsonParseException e) {
64+
throw new IllegalResourceFormatException("Failed to parse the specified JSON contents.", e);
65+
}
66+
return bundle;
67+
}
68+
69+
/**
70+
* The override of addBundleStrings is necessary because globalizejs treats arrays of strings
71+
* as a single long string, with each piece separated by a space, rather than treating it like
72+
* a real JSON array.
73+
*/
74+
@Override
75+
protected int addBundleStrings(JsonObject obj, String keyPrefix, Bundle bundle, int sequenceNum) {
76+
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
77+
String key = entry.getKey();
78+
JsonElement value = entry.getValue();
79+
if (value.isJsonObject()) {
80+
sequenceNum = addBundleStrings(value.getAsJsonObject(), modifiedKeyPrefix(keyPrefix, key, "$."), bundle,
81+
sequenceNum);
82+
} else if (value.isJsonArray()) {
83+
JsonArray ar = value.getAsJsonArray();
84+
StringBuilder sb = new StringBuilder();
85+
for (int i = 0; i < ar.size(); i++) {
86+
JsonElement arrayEntry = ar.get(i);
87+
if (arrayEntry.isJsonPrimitive() && arrayEntry.getAsJsonPrimitive().isString()) {
88+
if (i > 0) {
89+
sb.append(" "); //
90+
}
91+
sb.append(arrayEntry.getAsString());
92+
} else {
93+
throw new IllegalResourceFormatException(
94+
"Arrays must contain only strings in a globalizejs resource.");
95+
}
96+
}
97+
sequenceNum++;
98+
bundle.addResourceString(modifiedKeyPrefix(keyPrefix, key, ""), sb.toString(), sequenceNum);
99+
} else if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isString()) {
100+
throw new IllegalResourceFormatException("The value of JSON element " + key + " is not a string.");
101+
} else {
102+
sequenceNum++;
103+
bundle.addResourceString(modifiedKeyPrefix(keyPrefix, key, ""), value.getAsString(), sequenceNum);
104+
}
105+
}
106+
return sequenceNum;
107+
}
108+
}

gp-res-filter/src/main/java/com/ibm/g11n/pipeline/resfilter/JsonResource.java

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,38 +74,41 @@ public Bundle parse(InputStream inStream) throws IOException {
7474
return bundle;
7575
}
7676

77-
private int addBundleStrings(JsonObject obj, String keyPrefix, Bundle bundle, int sequenceNum) {
77+
protected int addBundleStrings(JsonObject obj, String keyPrefix, Bundle bundle, int sequenceNum) {
7878
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
7979
String key = entry.getKey();
8080
JsonElement value = entry.getValue();
8181
if (value.isJsonObject()) {
82-
sequenceNum = addBundleStrings(value.getAsJsonObject(), modifiedKeyPrefix(keyPrefix,key,"$.") , bundle, sequenceNum);
82+
sequenceNum = addBundleStrings(value.getAsJsonObject(), modifiedKeyPrefix(keyPrefix, key, "$."), bundle,
83+
sequenceNum);
8384
} else if (value.isJsonArray()) {
8485
JsonArray ar = value.getAsJsonArray();
8586
for (int i = 0; i < ar.size(); i++) {
8687
JsonElement arrayEntry = ar.get(i);
8788
if (arrayEntry.isJsonPrimitive() && arrayEntry.getAsJsonPrimitive().isString()) {
8889
sequenceNum++;
89-
bundle.addResourceString(modifiedKeyPrefix(keyPrefix,key,"$.") + "[" + Integer.toString(i) + "]",
90+
bundle.addResourceString(
91+
modifiedKeyPrefix(keyPrefix, key, "$.") + "[" + Integer.toString(i) + "]",
9092
arrayEntry.getAsString(), sequenceNum);
9193

9294
} else {
9395
sequenceNum = addBundleStrings(arrayEntry.getAsJsonObject(),
94-
modifiedKeyPrefix(keyPrefix,key,"$.") + "[" + Integer.toString(i) + "]", bundle, sequenceNum);
96+
modifiedKeyPrefix(keyPrefix, key, "$.") + "[" + Integer.toString(i) + "]", bundle,
97+
sequenceNum);
9598
}
9699
}
97100
} else if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isString()) {
98101
throw new IllegalResourceFormatException("The value of JSON element " + key + " is not a string.");
99102
} else {
100103
sequenceNum++;
101-
bundle.addResourceString(modifiedKeyPrefix(keyPrefix,key,""), value.getAsString(), sequenceNum);
104+
bundle.addResourceString(modifiedKeyPrefix(keyPrefix, key, ""), value.getAsString(), sequenceNum);
102105
}
103106
}
104107
return sequenceNum;
105108
}
106109

107-
private String modifiedKeyPrefix( String keyPrefix, String key, String addPrefixIfEmpty) {
108-
110+
protected String modifiedKeyPrefix(String keyPrefix, String key, String addPrefixIfEmpty) {
111+
109112
final Pattern specialSequences = Pattern.compile("[.'\\[\\]]");
110113
if (key.isEmpty()) {
111114
return keyPrefix;
@@ -120,19 +123,29 @@ private String modifiedKeyPrefix( String keyPrefix, String key, String addPrefix
120123
return keyPrefix + "." + key;
121124
}
122125
}
123-
126+
124127
@Override
125128
public void write(OutputStream outStream, String language, Bundle bundle) throws IOException {
126129
// extracts key value pairs in original sequence order
127130
TreeSet<ResourceString> sortedResources = new TreeSet<>(new ResourceStringComparator());
128131
sortedResources.addAll(bundle.getResourceStrings());
129132
JsonObject output = new JsonObject();
133+
JsonObject top_level;
134+
135+
if (this instanceof GlobalizeJsResource) {
136+
top_level = new JsonObject();
137+
top_level.add(language, output);
138+
} else {
139+
top_level = output;
140+
}
141+
130142
for (ResourceString res : sortedResources) {
131143
String key = res.getKey();
132144
List<KeyPiece> keyPieces = splitKeyPieces(key);
133145
JsonElement current = output;
134146
for (int i = 0; i < keyPieces.size(); i++) {
135-
if (i + 1 < keyPieces.size()) { // There is structure under this key piece
147+
if (i + 1 < keyPieces.size()) { // There is structure under this
148+
// key piece
136149
if (current.isJsonObject()) {
137150
JsonObject currentObject = current.getAsJsonObject();
138151
if (!currentObject.has(keyPieces.get(i).keyValue)) {
@@ -146,7 +159,7 @@ public void write(OutputStream outStream, String language, Bundle bundle) throws
146159
} else {
147160
JsonArray currentArray = current.getAsJsonArray();
148161
Integer idx = Integer.valueOf(keyPieces.get(i).keyValue);
149-
for ( int arrayIndex = currentArray.size(); arrayIndex <= idx ; arrayIndex++) {
162+
for (int arrayIndex = currentArray.size(); arrayIndex <= idx; arrayIndex++) {
150163
currentArray.add(JsonNull.INSTANCE);
151164
}
152165
if (currentArray.get(idx).isJsonNull()) {
@@ -155,15 +168,15 @@ public void write(OutputStream outStream, String language, Bundle bundle) throws
155168
} else {
156169
currentArray.set(idx, new JsonObject());
157170
}
158-
}
171+
}
159172
current = currentArray.get(idx);
160173
}
161174
} else { // This is the leaf node
162175
if (keyPieces.get(i).keyType == JsonToken.BEGIN_ARRAY) {
163176
JsonArray currentArray = current.getAsJsonArray();
164177
Integer idx = Integer.valueOf(keyPieces.get(i).keyValue);
165178
JsonPrimitive e = new JsonPrimitive(res.getValue());
166-
for ( int arrayIndex = currentArray.size(); arrayIndex <= idx ; arrayIndex++) {
179+
for (int arrayIndex = currentArray.size(); arrayIndex <= idx; arrayIndex++) {
167180
currentArray.add(JsonNull.INSTANCE);
168181
}
169182
current.getAsJsonArray().set(idx, e);
@@ -175,7 +188,7 @@ public void write(OutputStream outStream, String language, Bundle bundle) throws
175188
}
176189
try (OutputStreamWriter writer = new OutputStreamWriter(new BufferedOutputStream(outStream),
177190
StandardCharsets.UTF_8)) {
178-
new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(output, writer);
191+
new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(top_level, writer);
179192
}
180193
}
181194

@@ -209,10 +222,10 @@ private static List<String> findTokens(String data) {
209222
StringCharacterIterator i = new StringCharacterIterator(data);
210223
while (i.current() != StringCharacterIterator.DONE) {
211224
char c = i.current();
212-
if ( c == '\'' ) {
225+
if (c == '\'') {
213226
inQuotes = !inQuotes;
214227
}
215-
if (!inQuotes && ( c == '.' || c == '[' || c == ']')) {
228+
if (!inQuotes && (c == '.' || c == '[' || c == ']')) {
216229
tokens.add(currentToken.toString());
217230
currentToken.setLength(0);
218231
} else {

gp-res-filter/src/main/java/com/ibm/g11n/pipeline/resfilter/ResourceFilterFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2015
2+
* Copyright IBM Corp. 2015, 2017
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@ public static ResourceFilter getX(String type) {
2727
return new JavaPropertiesResource();
2828
} else if (type.equalsIgnoreCase("json")) {
2929
return new JsonResource();
30+
} else if (type.equalsIgnoreCase("globalizejs")) {
31+
return new GlobalizeJsResource();
3032
} else if (type.equalsIgnoreCase("amdjs")) {
3133
return new AmdJsResource();
3234
} else if (type.equalsIgnoreCase("yml")){
@@ -51,6 +53,8 @@ public static ResourceFilter get(ResourceType type) {
5153
return new JavaPropertiesResource();
5254
case JSON:
5355
return new JsonResource();
56+
case GLOBALIZEJS:
57+
return new GlobalizeJsResource();
5458
case AMDJS:
5559
return new AmdJsResource();
5660
case YML:

gp-res-filter/src/main/java/com/ibm/g11n/pipeline/resfilter/ResourceType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2015
2+
* Copyright IBM Corp. 2015, 2017
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
public enum ResourceType {
2424
JAVA,
2525
JSON,
26+
GLOBALIZEJS,
2627
AMDJS,
2728
YML,
2829
XLIFF,

gp-res-filter/src/test/java/com/ibm/g11n/pipeline/resfilter/AllTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2016
2+
* Copyright IBM Corp. 2016, 2017
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
@SuiteClasses({
2525
AmdJsResourceTest.class,
2626
AndroidStringsResourceTest.class,
27+
GlobalizeJsResourceTest.class,
2728
IOSStringsResourceTest.class,
2829
JavaPropertiesResourceTest.class,
2930
JsonResourceTest.class,

0 commit comments

Comments
 (0)