db4bdf6b2566418f7eed7ef73f480e8b16540234
[mir.git] / source / mircoders / localizer / basic / MirBasicProducerAssistantLocalizer.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mircoders.localizer.basic;
31
32 import java.io.ByteArrayInputStream;
33 import java.io.IOException;
34 import java.io.StringWriter;
35 import java.util.GregorianCalendar;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40
41 import gnu.regexp.RE;
42
43 import mir.config.MirPropertiesConfiguration;
44 import mir.entity.adapter.EntityAdapter;
45 import mir.entity.adapter.EntityIteratorAdapter;
46 import mir.generator.Generator;
47 import mir.generator.GeneratorExc;
48 import mir.generator.GeneratorFailure;
49 import mir.log.LoggerWrapper;
50 import mir.misc.StringUtil;
51 import mir.util.GeneratorDateTimeFunctions;
52 import mir.util.GeneratorFormatAdapters;
53 import mir.util.generator.ReflectionGeneratorFunctionsAdapter;
54 import mir.util.StringRoutines;
55 import mircoders.global.MirGlobal;
56 import mircoders.localizer.MirLocalizerExc;
57 import mircoders.localizer.MirLocalizerFailure;
58 import mircoders.localizer.MirProducerAssistantLocalizer;
59
60 import org.w3c.dom.Document;
61 import org.w3c.dom.NamedNodeMap;
62 import org.w3c.dom.Node;
63 import org.w3c.dom.NodeList;
64 import org.w3c.tidy.Configuration;
65 import org.w3c.tidy.Tidy;
66
67 public class MirBasicProducerAssistantLocalizer implements MirProducerAssistantLocalizer {
68   protected LoggerWrapper logger;
69
70   private RE regularExpressionLT;
71   private RE regularExpressionGT;
72   private RE regularExpressionWhitespace;
73
74   public MirBasicProducerAssistantLocalizer() throws MirLocalizerFailure {
75     try {
76       regularExpressionLT = new RE("<");
77       regularExpressionGT = new RE(">");
78       regularExpressionWhitespace = new RE("\\s+");
79     }
80     catch (Throwable t) {
81       throw new MirLocalizerFailure(t);
82     }
83   }
84
85   public void initializeGenerationValueSet(Map aValueSet) throws MirLocalizerExc, MirLocalizerFailure {
86     try {
87       Iterator i;
88
89       Map configMap = new HashMap();
90
91       logger = new LoggerWrapper("Localizer.ProducerAssistant");
92
93 // obsolete:
94       configMap.put("producerDocRoot", MirGlobal.config().getString("Producer.DocRoot"));
95       configMap.put("storageRoot", MirGlobal.config().getString("Producer.StorageRoot"));
96       configMap.put("productionHost", MirGlobal.config().getString("Producer.ProductionHost"));
97       configMap.put("openAction", MirGlobal.config().getString("Producer.OpenAction"));
98       configMap.put("docRoot", MirGlobal.config().getString("RootUri"));
99       configMap.put("actionRoot", MirGlobal.config().getString("RootUri") + "/servlet/Mir");
100       configMap.put("now", new GeneratorFormatAdapters.DateFormatAdapter(new GregorianCalendar().getTime(), MirGlobal.config().getString("Mir.DefaultTimezone")));
101       configMap.put("videoHost", MirGlobal.config().getString("Producer.Video.Host"));
102       configMap.put("audioHost", MirGlobal.config().getString("Producer.Audio.Host"));
103       configMap.put("imageHost", MirGlobal.config().getString("Producer.Image.Host"));
104       configMap.put("imagePath", MirGlobal.config().getString("Producer.Image.Path"));
105       configMap.put("mirVersion", MirGlobal.config().getString("Mir.Version"));
106       configMap.put("defEncoding", MirGlobal.config().getString("Mir.DefaultEncoding"));
107
108 // "new":
109       configMap.putAll(MirPropertiesConfiguration.instance().allSettings());
110
111       aValueSet.put("config", configMap);
112
113       aValueSet.put("utility", new Utility());
114
115       aValueSet.put("languages",
116           new EntityIteratorAdapter("", "", 20, MirGlobal.localizer().dataModel().adapterModel(), "language"));
117
118       aValueSet.put("topics",
119           new EntityIteratorAdapter("", "", 20, MirGlobal.localizer().dataModel().adapterModel(), "topic"));
120
121       Map articleTypeMap = new HashMap();
122       articleTypeMap.put("openposting", "0");
123       articleTypeMap.put("newswire", "1");
124       articleTypeMap.put("feature", "2");
125       articleTypeMap.put("topicspecial", "3");
126       articleTypeMap.put("startspecial", "4");
127
128       i = new EntityIteratorAdapter("", "", 20, MirGlobal.localizer().dataModel().adapterModel(), "articleType");
129       while (i.hasNext()) {
130         EntityAdapter articleType = (EntityAdapter) i.next();
131
132         articleTypeMap.put(articleType.get("name"), articleType.get("id"));
133       }
134       aValueSet.put("articletype", articleTypeMap);
135
136       Map commentStatusMap = new HashMap();
137       i = new EntityIteratorAdapter("", "", 20, MirGlobal.localizer().dataModel().adapterModel(), "commentStatus");
138       while (i.hasNext()) {
139         EntityAdapter commentStatus = (EntityAdapter) i.next();
140
141         commentStatusMap.put(commentStatus.get("name"), commentStatus.get("id"));
142       }
143       aValueSet.put("commentstatus", commentStatusMap);
144       aValueSet.put("languageCodeToId", new getLanguageIdFunction());
145     }
146     catch (Throwable t) {
147       logger.error("initializeGenerationValueSet: Exception while collecting comment statuses" + t.getMessage());
148
149       throw new MirLocalizerFailure(t);
150     }
151
152   }
153
154   public static class getLanguageIdFunction implements Generator.Function {
155     private Map languageCodeToId;
156     private String otherLanguageId;
157     private LoggerWrapper logger = new LoggerWrapper("Localizer.Earth.getLanguageIdFunction");
158
159     public getLanguageIdFunction() throws MirLocalizerFailure {
160       try {
161         otherLanguageId = "";
162         languageCodeToId = new HashMap();
163
164         Iterator i = new EntityIteratorAdapter("", "", 20, MirGlobal.localizer().dataModel().adapterModel(), "language");
165         while (i.hasNext()) {
166           EntityAdapter language = (EntityAdapter) i.next();
167           if (language.get("code").equals("ot")) {
168             otherLanguageId = (String) language.get("id");
169           }
170
171           languageCodeToId.put(language.get("code"), language.get("id"));
172         }
173       }
174       catch (Throwable t) {
175         logger.error(t.toString());
176
177         throw new MirLocalizerFailure(t);
178       }
179     }
180
181     public Object perform(List aParameters) throws GeneratorExc, GeneratorFailure {
182       try {
183         if (aParameters.size() != 1) {
184           throw new GeneratorExc("getLanguageIdFunction: 1 parameter expected: language-code");
185         }
186
187         String result = (String) languageCodeToId.get(aParameters.get(0));
188         if (result == null) {
189           result = otherLanguageId;
190         }
191
192         return result;
193       }
194       catch (GeneratorExc e) {
195         throw e;
196       }
197       catch (Throwable t) {
198         throw new GeneratorFailure("getLanguageIdFunction: " + t.getMessage(), t);
199       }
200     }
201   }
202
203
204   public String filterNonHTMLText(String aText) {
205
206     logger.debug("about to filter non HTML Text of length " + aText.length());
207     try {
208       String result =
209           StringUtil.createHTML(
210               StringUtil.removeHTMLTags(aText),
211               MirGlobal.config().getString("Producer.ImageRoot"),
212               MirGlobal.config().getString("Producer.MailLinkName"),
213               MirGlobal.config().getString("Producer.ExtLinkName"),
214               MirGlobal.config().getString("Producer.IntLinkName"));
215       logger.debug("done filtering non-HTML text ");
216       return result;
217     }
218     catch (Throwable t) {
219       logger.error("error while filtering non-HTML text: " + t.toString());
220
221       throw new RuntimeException(t.toString());
222     }
223   }
224
225   public Generator.Interceptor createGenerationInterceptor() throws MirLocalizerExc, MirLocalizerFailure {
226
227     if (MirGlobal.config().getBoolean("Mir.Producer.UseInterceptor", true)) {
228       return new Generator.Interceptor() {
229
230         public Object intercept(Object anObject) {
231           if (anObject instanceof EntityAdapter) {
232             return new InterceptedEntityAdapter((EntityAdapter) anObject);
233           }
234
235           return anObject;
236         }
237       };
238     }
239     else {
240       return null;
241     }
242   }
243
244   public class InterceptedEntityAdapter {
245     private EntityAdapter adapter;
246
247     InterceptedEntityAdapter(EntityAdapter anEntityAdapter) {
248       adapter = anEntityAdapter;
249     }
250
251     public Object get(String aField) {
252       Object result = adapter.get(aField);
253       if (result instanceof String) {
254         return filterHTMLText((String) result);
255       }
256       else {
257         return result;
258       }
259     }
260
261     public Object getRaw() {
262       return new RawEntityAdapter(adapter);
263     }
264   }
265
266   public class RawEntityAdapter {
267     private EntityAdapter adapter;
268
269     RawEntityAdapter(EntityAdapter anEntityAdapter) {
270       adapter = anEntityAdapter;
271     }
272
273     public Object get(String aField) {
274       return adapter.get(aField);
275     }
276   }
277
278   public String filterHTMLText(String aText) {
279     try {
280       StringWriter out = new StringWriter();
281       Tidy tidy = new Tidy();
282       ByteArrayInputStream in = new ByteArrayInputStream(aText.getBytes("UTF8"));
283       tidy.setMakeClean(true);
284       tidy.setCharEncoding(Configuration.UTF8);
285       tidy.setErrout(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
286       print(tidy.parseDOM(in, null), out);
287
288       return out.toString();
289     }
290     catch (IOException e) {
291       return e.getMessage();
292     }
293   }
294
295
296   private String[] badAttributeValuePrefixes = {
297       "javascript", "vbscript", "about", "wysiwyg", "data", "view-source",
298       "ms-its", "mhtml", "shell", "lynxexec", "lynxcgi", "hcp", "ms-help",
299       "help", "disk", "vnd.ms.radio", "opera", "res", "resource", "chrome",
300       "mocha", "livescript"};
301
302
303   private String[] badAttributes = {
304       "onabort", "onblur", "onchange", "onclick", "ondblclick", "onerror",
305       "onfocus", "onkeydown", "onKeypress", "onkeyup", "onload", "onmousedown",
306       "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onreset",
307       "onselect", "onsubmit", "onunload", "onload", "onclick", "onfocus",
308       "onblur", "FSCommand", "onAbort", "onActivate", "onAfterPrint",
309       "onAfterUpdate", "onBeforeActivate", "onBeforeCopy", "onBeforeCut",
310       "onBeforeDeactivate", "onBeforeEditFocus", "onBeforePaste",
311       "onBeforePrint", "onBeforeUnload", "onBegin", "onBlur", "onBounce",
312       "onCellChange", "onChange", "onClick", "onContextMenu", "onControlSelect",
313       "onCopy", "onCut", "onDataAvailible", "onDataSetChanged", "onDataSetComplete",
314       "onDblClick", "onDeactivate", "onDrag", "onDragEnd", "onDragLeave", "onDragEnter",
315       "onDragOver", "onDragDrop", "onDrop", "onEnd", "onError", "onErrorUpdate", "onExit",
316       "onFilterChange", "onFinish", "onFocus", "onFocusIn", "onFocusOut", "onHelp",
317       "onKeyDown", "onKeyPress", "onKeyUp", "onLayoutComplete", "onLoad", "onLoseCapture",
318       "onMediaComplete", "onMediaError", "onMouseDown", "onMouseEnter", "onMouseLeave",
319       "onMouseMove", "onMouseOut", "onMouseOver", "onMouseUp", "onMouseWheel", "onMove",
320       "onMoveEnd", "onMoveStart", "onOutOfSync", "onPaste", "onPause", "onProgress",
321       "onPropertyChange", "onReadyStateChange", "onRepeat", "onReset", "onResize",
322       "onResizeEnd", "onResizeStart", "onResume", "onReverse", "onRowEnter", "onRowExit",
323       "onRowDelete", "onRowInserted", "onScroll", "onSeek", "onSelect", "onSelectionChange",
324       "onSelectStart", "onStart", "onStop", "onSynchRestored", "onSubmit", "onTimeError",
325       "onTrackChange", "onUnload", "onURLFlip", "seekSegmentTime", "style", "height", "width"};
326
327   private boolean isBadAttr(String attrName) {
328     for (int i = 0; i < badAttributes.length; i++) {
329       if (badAttributes[i].toLowerCase().equals(attrName.toLowerCase())) {
330         return true;
331       }
332     }
333     return false;
334   }
335
336   private String stripWhitespace(String aString) {
337     try {
338       return regularExpressionWhitespace.substituteAll(aString, "");
339     }
340     catch (Throwable t) {
341       return "";
342     }
343   }
344
345   private boolean checkAttr(String attrName) {
346     if (isBadAttr(attrName)) {
347       return false;
348     }
349     return true;
350
351   }
352
353   private boolean checkAttrValue(String attrValue) {
354     for (int i = 0; i < badAttributeValuePrefixes.length; i++) {
355       if ((stripWhitespace(attrValue.toLowerCase())).startsWith(badAttributeValuePrefixes[i].toLowerCase() + ":")) {
356         return false;
357       }
358     }
359     return true;
360   }
361
362
363   private boolean checkNode(String nodeName) {
364     List languages = StringRoutines.splitString(MirGlobal.config().getString("Localizer.HTML.Whitelist"), ";");
365
366     Iterator i = languages.iterator();
367     while (i.hasNext()) {
368       if (nodeName.equals(i.next())) {
369         return true;
370       }
371     }
372     return false;
373   }
374
375   private void print(Node node, StringWriter out) throws IOException {
376     if (node == null) {
377       return;
378     }
379     int type = node.getNodeType();
380     boolean canOutput = checkNode(node.getNodeName());
381
382     switch (type) {
383
384       case Node.DOCUMENT_NODE:
385
386         print(((Document) node).getDocumentElement(), out);
387         out.flush();
388         break;
389
390       case Node.ELEMENT_NODE:
391         if (canOutput) {
392           out.write('<');
393
394           out.write(node.getNodeName());
395           NamedNodeMap attrs = node.getAttributes();
396
397           for (int i = 0; i < attrs.getLength(); i++) {
398             String attrName = attrs.item(i).getNodeName();
399             String attrValue = attrs.item(i).getNodeValue();
400             if (checkAttr(attrName) && checkAttrValue(attrValue)) {
401               out.write(' ');
402               out.write(attrs.item(i).getNodeName());
403               out.write("=\"");
404
405               out.write(attrs.item(i).getNodeValue());
406               out.write('"');
407             }
408           }
409
410           if (node.getChildNodes() == null || node.getChildNodes().getLength() == 0) {
411             out.write("/");
412           }
413           out.write('>');
414         }
415         NodeList children = node.getChildNodes();
416         if (children != null) {
417           int len = children.getLength();
418           for (int i = 0; i < len; i++) {
419             print(children.item(i), out);
420           }
421         }
422         break;
423
424       case Node.TEXT_NODE:
425         String value = node.getNodeValue();
426         try {
427           value = regularExpressionLT.substituteAll(value, "&lt;");
428           value = regularExpressionGT.substituteAll(value, "&gt;");
429         }
430         catch (Throwable t) {
431           value = "";
432         }
433         out.write(value);
434
435         break;
436
437     }
438
439     if (type == Node.ELEMENT_NODE && canOutput && node.getChildNodes() != null && node.getChildNodes().getLength() > 0) {
440       out.write("</");
441       out.write(node.getNodeName());
442       out.write('>');
443     }
444
445     out.flush();
446   }
447
448   public static class Utility extends ReflectionGeneratorFunctionsAdapter {
449     public Utility() {
450       super(new MirBasicUtilityFunctions());
451     }
452
453     public Object getDatetime() {
454       return new GeneratorDateTimeFunctions.DateTimeFunctions(
455           MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone"));
456     }
457
458     public Object getCompressWhitespace() {
459       return new freemarker.template.utility.CompressWhitespace();
460     }
461   }
462 }