15 |
15 |
import java.util.Comparator;
|
16 |
16 |
import java.util.Enumeration;
|
17 |
17 |
import java.util.HashMap;
|
18 |
|
import java.util.LinkedHashSet;
|
19 |
18 |
import java.util.Locale;
|
20 |
19 |
import java.util.Map;
|
21 |
20 |
import java.util.Properties;
|
... | ... | |
26 |
25 |
|
27 |
26 |
import org.apache.commons.io.FilenameUtils;
|
28 |
27 |
import org.apache.commons.lang.StringUtils;
|
29 |
|
import org.eclipse.osgi.util.NLS;
|
30 |
28 |
import org.txm.rcp.translate.devtools.NormalizeKeys;
|
31 |
29 |
import org.txm.utils.BiHashMap;
|
32 |
30 |
import org.txm.utils.io.IOUtils;
|
... | ... | |
45 |
43 |
*/
|
46 |
44 |
public static String ENCODING = "UTF-8";
|
47 |
45 |
|
|
46 |
public final static String FILE_SEPARATOR = System.getProperty("file.separator");
|
|
47 |
|
48 |
48 |
/**
|
49 |
49 |
* Debug state.
|
50 |
50 |
*/
|
... | ... | |
72 |
72 |
|
73 |
73 |
|
74 |
74 |
/**
|
75 |
|
* TODO Global REGEX containing all keys to later use for search in files.
|
|
75 |
* Global REGEX containing all keys to later use for search in files.
|
76 |
76 |
*/
|
77 |
|
protected Pattern keysGlobalREGEX = Pattern.compile("...");
|
|
77 |
protected Pattern keysGlobalREGEX;
|
78 |
78 |
|
79 |
79 |
/**
|
80 |
80 |
* Index of the keys used and their associated files.
|
... | ... | |
137 |
137 |
* @throws IOException
|
138 |
138 |
*/
|
139 |
139 |
public PluginMessagesManager(File projectDirectory) throws UnsupportedEncodingException, FileNotFoundException, IOException {
|
140 |
|
this(projectDirectory, findMessageFile(projectDirectory), false);
|
|
140 |
this(projectDirectory, false);
|
141 |
141 |
}
|
142 |
142 |
|
143 |
|
public static File findMessageFile(File projectDirectory2) {
|
144 |
|
File messagesPackageDir = new File(projectDirectory2, "src/"+projectDirectory2.getName().replaceAll("\\.", "/")+"/messages");
|
|
143 |
/**
|
|
144 |
*
|
|
145 |
* @param projectDirectory
|
|
146 |
* @param debug
|
|
147 |
* @throws UnsupportedEncodingException
|
|
148 |
* @throws FileNotFoundException
|
|
149 |
* @throws IOException
|
|
150 |
*/
|
|
151 |
public PluginMessagesManager(File projectDirectory, boolean debug) throws UnsupportedEncodingException, FileNotFoundException, IOException {
|
|
152 |
this(projectDirectory, findMessageFile(projectDirectory), debug);
|
|
153 |
}
|
145 |
154 |
|
146 |
|
if (!messagesPackageDir.exists()) {
|
147 |
|
messagesPackageDir = new File(projectDirectory2, "src/java/"+projectDirectory2.getName().replaceAll("\\.", "/")+"/messages");
|
148 |
|
}
|
149 |
155 |
|
150 |
|
if (!messagesPackageDir.exists()) {
|
151 |
|
messagesPackageDir = new File(projectDirectory2, "src/main/java/"+projectDirectory2.getName().replaceAll("\\.", "/")+"/messages");
|
152 |
|
}
|
153 |
|
|
154 |
|
if (!messagesPackageDir.exists()) {
|
155 |
|
return null;
|
156 |
|
}
|
157 |
|
|
158 |
|
for (File messagesJavaFile : messagesPackageDir.listFiles()) {
|
159 |
|
if (messagesJavaFile.isDirectory()) continue;
|
160 |
|
if (!messagesJavaFile.getName().endsWith("Messages.java")) continue;
|
161 |
|
return messagesJavaFile;
|
162 |
|
}
|
163 |
|
return null;
|
164 |
|
}
|
165 |
|
|
|
156 |
|
166 |
157 |
/**
|
167 |
158 |
*
|
168 |
159 |
* @param projectDirectory
|
... | ... | |
175 |
166 |
this(projectDirectory, javaMessageFile, false);
|
176 |
167 |
}
|
177 |
168 |
|
|
169 |
|
|
170 |
|
178 |
171 |
/**
|
179 |
172 |
*
|
180 |
173 |
* @param projectDirectory
|
... | ... | |
232 |
225 |
}
|
233 |
226 |
if (line2.endsWith(";") && !line2.contains("=") && !line2.contains("\"")) {
|
234 |
227 |
line2 = line2.substring(21, line2.length() -1);
|
|
228 |
|
|
229 |
if(debug) {
|
|
230 |
System.out.println("PluginMessagesManager.PluginMessagesManager(): messages key: " + line2 + " added.");
|
|
231 |
}
|
235 |
232 |
messageKeys.add(line2);
|
236 |
233 |
}
|
237 |
234 |
}
|
... | ... | |
239 |
236 |
line2 = reader2.readLine();
|
240 |
237 |
}
|
241 |
238 |
reader2.close();
|
242 |
|
}
|
|
239 |
|
|
240 |
|
|
241 |
if(debug) {
|
|
242 |
System.out.println("PluginMessagesManager.PluginMessagesManager(): number of keys: " + this.messageKeys.size() + ".");
|
|
243 |
}
|
|
244 |
|
243 |
245 |
|
244 |
|
// create the project source files list
|
245 |
|
this.createSourceFilesList();
|
|
246 |
// create the project source files list
|
|
247 |
this.createSourceFilesList();
|
246 |
248 |
|
247 |
|
//this.createKeysGlobalREGEX();
|
248 |
|
this.createUsedKeysFilesIndex();
|
|
249 |
if(messageKeys.size() > 0) {
|
|
250 |
//this.createKeysGlobalREGEX();
|
|
251 |
this.createUsedKeysFilesIndex();
|
|
252 |
}
|
249 |
253 |
|
250 |
|
// try loading OSGI messages
|
251 |
|
if (pluginXMLFile.exists()) {
|
252 |
|
ArrayList<String> result = IOUtils.findWithGroup(pluginXMLFile, "\"%([^\"]+)\"");
|
253 |
|
osgiKeys.addAll(result);
|
|
254 |
// try loading OSGI messages
|
|
255 |
if (pluginXMLFile.exists()) {
|
|
256 |
ArrayList<String> result = IOUtils.findWithGroup(pluginXMLFile, "\"%([^\"]+)\"");
|
|
257 |
osgiKeys.addAll(result);
|
254 |
258 |
|
255 |
|
File osgiInf = new File(projectDirectory, "OSGI-INF/l10n");
|
256 |
|
if (osgiInf.exists()) {
|
257 |
|
for (File propFile : osgiInf.listFiles()) {
|
258 |
|
if (propFile.getName().startsWith("bundle") && propFile.getName().endsWith(".properties")) {
|
259 |
|
Properties props = new Properties();
|
260 |
|
props.load(IOUtils.getReader(propFile));
|
261 |
|
HashMap<String, String> h = new HashMap<String, String>();
|
262 |
|
for (Object k : props.keySet()) h.put(k.toString(), props.getProperty(k.toString()));
|
263 |
|
osgiLangs.put(propFile, h);
|
|
259 |
File osgiInf = new File(projectDirectory, "OSGI-INF/l10n");
|
|
260 |
if (osgiInf.exists()) {
|
|
261 |
for (File propFile : osgiInf.listFiles()) {
|
|
262 |
if (propFile.getName().startsWith("bundle") && propFile.getName().endsWith(".properties")) {
|
|
263 |
Properties props = new Properties();
|
|
264 |
props.load(IOUtils.getReader(propFile));
|
|
265 |
HashMap<String, String> h = new HashMap<String, String>();
|
|
266 |
for (Object k : props.keySet()) h.put(k.toString(), props.getProperty(k.toString()));
|
|
267 |
osgiLangs.put(propFile, h);
|
|
268 |
}
|
264 |
269 |
}
|
265 |
270 |
}
|
|
271 |
// System.out.println(osgiKeys);
|
|
272 |
// System.out.println(osgiLangs);
|
266 |
273 |
}
|
267 |
|
// System.out.println(osgiKeys);
|
268 |
|
// System.out.println(osgiLangs);
|
269 |
|
}
|
270 |
274 |
|
271 |
|
if(debug) {
|
272 |
|
this.dumpUsedKeysFilesIndex();
|
|
275 |
if(debug) {
|
|
276 |
this.dumpUsedKeysFilesIndex();
|
|
277 |
}
|
273 |
278 |
}
|
|
279 |
else {
|
|
280 |
System.err.println("PluginMessagesManager.PluginMessagesManager(): skipped, java message file not found for project " + this.projectDirectory + ".");
|
|
281 |
}
|
274 |
282 |
}
|
275 |
283 |
|
276 |
|
// /**
|
277 |
|
// * Creates a global REGEX containing all keys to later search in files.
|
278 |
|
// */
|
279 |
|
// public void createKeysGlobalREGEX() {
|
280 |
|
// StringBuffer buffer = new StringBuffer();
|
281 |
|
// for (String key : messageKeys) {
|
282 |
|
// buffer.append("|" + key);
|
283 |
|
// }
|
284 |
|
//
|
285 |
|
// // remove first pipe
|
286 |
|
// buffer.deleteCharAt(0);
|
287 |
|
//
|
288 |
|
// this.keysGlobalREGEX = Pattern.compile("(" + buffer + ")");
|
289 |
|
//
|
290 |
|
// if(debug) {
|
291 |
|
// System.out.println("PluginMessagesManager.createKeysGlobalREGEX(): REGEX created: " + this.keysGlobalREGEX + ".");
|
292 |
|
// }
|
293 |
|
//
|
294 |
|
// }
|
|
284 |
// /**
|
|
285 |
// * Creates a global REGEX containing all keys to later search in files.
|
|
286 |
// */
|
|
287 |
// public void createKeysGlobalREGEX() {
|
|
288 |
//
|
|
289 |
// if(messageKeys.size() == 0) {
|
|
290 |
// return;
|
|
291 |
// }
|
|
292 |
//
|
|
293 |
// StringBuffer buffer = new StringBuffer();
|
|
294 |
// buffer.append(this.getMessageClassName() + "\\\\.(");
|
|
295 |
//
|
|
296 |
// int i = 0;
|
|
297 |
// for (String key : messageKeys) {
|
|
298 |
// if(i > 0) {
|
|
299 |
// buffer.append("|" + key);
|
|
300 |
// }
|
|
301 |
// else {
|
|
302 |
// buffer.append(key);
|
|
303 |
// }
|
|
304 |
// i++;
|
|
305 |
// }
|
|
306 |
//
|
|
307 |
// // remove first pipe
|
|
308 |
// //buffer.deleteCharAt(0);
|
|
309 |
//
|
|
310 |
// this.keysGlobalREGEX = Pattern.compile("(" + buffer + "))");
|
|
311 |
//
|
|
312 |
// if(debug) {
|
|
313 |
// System.out.println("PluginMessagesManager.createKeysGlobalREGEX(): REGEX created: " + this.keysGlobalREGEX + ".");
|
|
314 |
// }
|
|
315 |
//
|
|
316 |
// }
|
295 |
317 |
|
|
318 |
|
|
319 |
/**
|
|
320 |
* Creates a global REGEX containing all keys to later search in files.
|
|
321 |
*/
|
|
322 |
public void createKeysGlobalREGEX() {
|
|
323 |
StringBuffer buffer = new StringBuffer();
|
|
324 |
for (String key : messageKeys) {
|
|
325 |
buffer.append("|" + this.getKeyFullName(key).replaceAll("\\.", "\\\\."));
|
|
326 |
}
|
|
327 |
|
|
328 |
// remove first pipe
|
|
329 |
buffer.deleteCharAt(0);
|
|
330 |
|
|
331 |
this.keysGlobalREGEX = Pattern.compile("(" + buffer + ")");
|
|
332 |
|
|
333 |
if(debug) {
|
|
334 |
System.out.println("PluginMessagesManager.createKeysGlobalREGEX(): REGEX created: " + this.keysGlobalREGEX + ".");
|
|
335 |
}
|
|
336 |
|
|
337 |
}
|
|
338 |
|
|
339 |
|
|
340 |
|
|
341 |
public static File findMessageFile(File projectDirectory2) {
|
|
342 |
File messagesPackageDir = new File(projectDirectory2, "src/"+projectDirectory2.getName().replaceAll("\\.", "/")+"/messages");
|
|
343 |
|
|
344 |
if (!messagesPackageDir.exists()) {
|
|
345 |
messagesPackageDir = new File(projectDirectory2, "src/java/"+projectDirectory2.getName().replaceAll("\\.", "/")+"/messages");
|
|
346 |
}
|
|
347 |
|
|
348 |
if (!messagesPackageDir.exists()) {
|
|
349 |
messagesPackageDir = new File(projectDirectory2, "src/main/java/"+projectDirectory2.getName().replaceAll("\\.", "/")+"/messages");
|
|
350 |
}
|
|
351 |
|
|
352 |
if (!messagesPackageDir.exists()) {
|
|
353 |
return null;
|
|
354 |
}
|
|
355 |
|
|
356 |
for (File messagesJavaFile : messagesPackageDir.listFiles()) {
|
|
357 |
if (messagesJavaFile.isDirectory()) continue;
|
|
358 |
if (!messagesJavaFile.getName().endsWith("Messages.java")) continue;
|
|
359 |
return messagesJavaFile;
|
|
360 |
}
|
|
361 |
return null;
|
|
362 |
}
|
|
363 |
|
|
364 |
// public static File findMessageFile(File projectDirectory2) {
|
|
365 |
//// System.out.println("PluginMessagesManager.findMessageFile() " + FILE_SEPARATOR);
|
|
366 |
// File messagesPackageDir = new File(projectDirectory2, "src" + FILE_SEPARATOR + projectDirectory2.getName().replaceAll("\\.", "\\" + FILE_SEPARATOR) + FILE_SEPARATOR + "messages");
|
|
367 |
//
|
|
368 |
// if (!messagesPackageDir.exists()) {
|
|
369 |
// messagesPackageDir = new File(projectDirectory2, "src" + FILE_SEPARATOR + "java" + FILE_SEPARATOR + projectDirectory2.getName().replaceAll("\\.", "\\" + FILE_SEPARATOR) + FILE_SEPARATOR + "messages");
|
|
370 |
// }
|
|
371 |
//
|
|
372 |
// if (!messagesPackageDir.exists()) {
|
|
373 |
// messagesPackageDir = new File(projectDirectory2, "src" + FILE_SEPARATOR + "main" + FILE_SEPARATOR + "java" + FILE_SEPARATOR + projectDirectory2.getName().replaceAll("\\.", "\\" + FILE_SEPARATOR) + FILE_SEPARATOR + "messages");
|
|
374 |
// }
|
|
375 |
//
|
|
376 |
// if (!messagesPackageDir.exists()) {
|
|
377 |
// return null;
|
|
378 |
// }
|
|
379 |
//
|
|
380 |
// for (File messagesJavaFile : messagesPackageDir.listFiles()) {
|
|
381 |
// if (messagesJavaFile.isDirectory()) continue;
|
|
382 |
// if (!messagesJavaFile.getName().endsWith("Messages.java")) continue;
|
|
383 |
// return messagesJavaFile;
|
|
384 |
// }
|
|
385 |
// return null;
|
|
386 |
// }
|
|
387 |
|
|
388 |
|
|
389 |
|
296 |
390 |
/**
|
297 |
391 |
* Creates/updates the index of the keys used and their associated files.
|
298 |
392 |
* @throws IOException
|
... | ... | |
300 |
394 |
public void createUsedKeysFilesIndex() throws IOException {
|
301 |
395 |
this.usedKeysFilesIndex = new TreeMap<String, TreeSet<File>>();
|
302 |
396 |
|
303 |
|
//for (String key : messageKeys) {
|
|
397 |
// Old version, based on real key name
|
|
398 |
// for (File file : this.srcFiles) {
|
|
399 |
// for (String key : messageKeys) {
|
|
400 |
// if(debug) {
|
|
401 |
// System.out.println("PluginMessagesManager.createUsedKeysIndex(): looking for key " + this.getKeyFullName(key) + " in project source files...");
|
|
402 |
// }
|
|
403 |
// String lines = IOUtils.getText(file, PluginMessagesManager.ENCODING);
|
|
404 |
// if(lines.contains(this.getKeyFullName(key))) {
|
|
405 |
// if (!this.usedKeysFilesIndex.containsKey(key)) {
|
|
406 |
// this.usedKeysFilesIndex.put(key, new TreeSet<File>());
|
|
407 |
// }
|
|
408 |
// this.usedKeysFilesIndex.get(key).add(file);
|
|
409 |
// };
|
|
410 |
// }
|
|
411 |
// }
|
304 |
412 |
|
305 |
|
// if(debug) {
|
306 |
|
// System.out.println("PluginMessagesManager.createUsedKeysIndex(): looking for key " + this.getKeyFullName(key) + " in project source files...");
|
307 |
|
// }
|
308 |
|
|
|
413 |
// New version using REGEX
|
309 |
414 |
for (File file : this.srcFiles) {
|
310 |
415 |
if (!file.exists()) continue;
|
311 |
416 |
String lines = IOUtils.getText(file, PluginMessagesManager.ENCODING);
|
312 |
417 |
|
313 |
|
// for (String line : lines) {
|
314 |
|
// // if(line.contains(this.getKeyFullName(key))) {
|
315 |
|
// // files.add(file);
|
316 |
|
// // };
|
317 |
|
|
318 |
418 |
Matcher m = WorkspaceMessagesManager.KEY_REGEX.matcher(lines);
|
|
419 |
//Matcher m = this.keysGlobalREGEX.matcher(lines);
|
319 |
420 |
while (m.find()) {
|
320 |
421 |
// Get the matching string
|
321 |
422 |
String key = m.group();
|
|
423 |
|
322 |
424 |
if (!this.usedKeysFilesIndex.containsKey(key)) {
|
323 |
425 |
this.usedKeysFilesIndex.put(key, new TreeSet<File>());
|
324 |
426 |
}
|
325 |
427 |
this.usedKeysFilesIndex.get(key).add(file);
|
|
428 |
|
|
429 |
if(debug) {
|
|
430 |
System.out.println("PluginMessagesManager.createUsedKeysFilesIndex(): messages key: " + key + " added for file " + file + ".");
|
|
431 |
}
|
|
432 |
|
326 |
433 |
}
|
327 |
|
// }
|
|
434 |
|
328 |
435 |
}
|
329 |
|
//}
|
330 |
436 |
}
|
331 |
437 |
|
332 |
438 |
|
... | ... | |
337 |
443 |
|
338 |
444 |
System.out.println("PluginMessagesManager.dumpUsedKeysIndex(): dumping used keys files index...");
|
339 |
445 |
|
|
446 |
if(this.usedKeysFilesIndex == null) {
|
|
447 |
System.out.println("PluginMessagesManager.dumpUsedKeysIndex(): project have no used keys.");
|
|
448 |
return;
|
|
449 |
}
|
|
450 |
|
340 |
451 |
for (Map.Entry<String, TreeSet<File>> entry : this.usedKeysFilesIndex.entrySet()) {
|
341 |
|
System.out.println("key: " + this.getKeyFullName(entry.getKey()));
|
|
452 |
System.out.println("key: " + entry.getKey());
|
342 |
453 |
if(entry.getValue().isEmpty()) {
|
343 |
454 |
System.out.println(" file(s): -not used in any file of the project " + this.getProjectDirectory() + "-");
|
344 |
455 |
}
|
... | ... | |
348 |
459 |
}
|
349 |
460 |
}
|
350 |
461 |
}
|
|
462 |
|
|
463 |
System.out.println("PluginMessagesManager.dumpUsedKeysIndex(): number of used keys: " + this.usedKeysFilesIndex.size() + ".");
|
351 |
464 |
}
|
352 |
465 |
|
353 |
466 |
/***
|
... | ... | |
439 |
552 |
}
|
440 |
553 |
|
441 |
554 |
public String get(String lang, String key) {
|
442 |
|
File p = new File(javaMessageFile.getParentFile(), "messages"+lang+".properties");
|
|
555 |
File p = new File(javaMessageFile.getParentFile(), "messages" + lang + ".properties");
|
443 |
556 |
|
444 |
557 |
if (!file2lang.containsKey(p)) return null;
|
445 |
558 |
|
... | ... | |
456 |
569 |
|
457 |
570 |
public String getMessageClassName() {
|
458 |
571 |
if (javaMessageFile == null) return "";
|
459 |
|
return javaMessageFile.getName().substring(0, javaMessageFile.getName().length()-5);
|
|
572 |
return javaMessageFile.getName().substring(0, javaMessageFile.getName().length() - 5);
|
460 |
573 |
}
|
461 |
574 |
|
462 |
575 |
public String getMessageFullClassName() {
|
463 |
576 |
if (javaMessageFile == null) return "";
|
464 |
|
return javaMessageFile.getAbsolutePath().substring(javaMessageFile.getAbsolutePath().lastIndexOf("org/txm/"), javaMessageFile.getAbsolutePath().length()-5).replace("/", ".");
|
|
577 |
return javaMessageFile.getAbsolutePath().substring(javaMessageFile.getAbsolutePath().lastIndexOf("org" + FILE_SEPARATOR + "txm" + FILE_SEPARATOR),
|
|
578 |
javaMessageFile.getAbsolutePath().length() - 5).replace(FILE_SEPARATOR, ".");
|
465 |
579 |
}
|
466 |
580 |
|
467 |
581 |
/**
|
... | ... | |
474 |
588 |
|
475 |
589 |
public String getMessageFullName() {
|
476 |
590 |
if (javaMessageFile == null) return "";
|
477 |
|
return javaMessageFile.getAbsolutePath().substring(javaMessageFile.getAbsolutePath().lastIndexOf("org/txm/")+8, javaMessageFile.getAbsolutePath().length()-5-8).replace("/", ".");
|
|
591 |
return javaMessageFile.getAbsolutePath().substring(javaMessageFile.getAbsolutePath().lastIndexOf("org" + FILE_SEPARATOR + "txm" + FILE_SEPARATOR) + 8,
|
|
592 |
javaMessageFile.getAbsolutePath().length() - 5 - 8).replace(FILE_SEPARATOR, ".");
|
478 |
593 |
}
|
479 |
594 |
|
480 |
595 |
/**
|
... | ... | |
521 |
636 |
propFile.delete();
|
522 |
637 |
newPropFile = propFile;
|
523 |
638 |
}
|
524 |
|
props.store(IOUtils.getWriter(newPropFile, ENCODING), "TXM messages generated by the PluginMessages class");
|
|
639 |
props.store(IOUtils.getWriter(newPropFile, ENCODING), "TXM messages generated by the PluginMessagesManager class");
|
525 |
640 |
}
|
526 |
641 |
|
527 |
642 |
// write message File
|
... | ... | |
601 |
716 |
}
|
602 |
717 |
|
603 |
718 |
/**
|
604 |
|
* Parses the project and stores a list of the source files.
|
|
719 |
* Parses the specified path and stores a list of the source files.
|
605 |
720 |
*/
|
606 |
721 |
private void createSourceFilesList(File path) {
|
607 |
722 |
File[] list = path.listFiles();
|
... | ... | |
786 |
901 |
*/
|
787 |
902 |
public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException, IOException {
|
788 |
903 |
|
789 |
|
File projectFile = new File(new File(System.getProperty("user.dir")).getParentFile().getAbsolutePath() + "/org.txm.rcp");
|
|
904 |
long startTime = System.currentTimeMillis();
|
|
905 |
|
|
906 |
//File projectFile = new File(new File(System.getProperty("user.dir")).getParentFile().getAbsolutePath() + "/org.txm.rcp");
|
|
907 |
File projectFile = new File(new File(System.getProperty("user.dir")).getParentFile().getAbsolutePath() + "/org.txm.core");
|
790 |
908 |
|
791 |
|
PluginMessagesManager pmManager = new PluginMessagesManager(projectFile);
|
|
909 |
PluginMessagesManager pmManager = new PluginMessagesManager(projectFile, true);
|
792 |
910 |
|
793 |
911 |
// test to find files using the specified key
|
794 |
912 |
/*
|
... | ... | |
816 |
934 |
pmManager.saveChanges(false);
|
817 |
935 |
|
818 |
936 |
System.out.println("dir="+pmManager.getProjectDirectory());
|
|
937 |
System.out.println("Elapsed time:"+ ((double)(System.currentTimeMillis()-startTime)/1000));
|
|
938 |
|
819 |
939 |
System.out.println("PluginMessagesManager.main(): done.");
|
820 |
940 |
}
|
821 |
941 |
}
|