View Javadoc

1   package sk.stuba.fiit.foo07.genex.export;
2   
3   import java.io.BufferedReader;
4   import java.io.BufferedWriter;
5   import java.io.FileOutputStream;
6   import java.io.IOException;
7   import java.io.InputStreamReader;
8   import java.io.OutputStreamWriter;
9   import java.io.StringReader;
10  import java.sql.Connection;
11  import java.util.ArrayList;
12  
13  import net.iharder.base64.Base64;
14  
15  import org.w3c.dom.Document;
16  import org.w3c.dom.Element;
17  import org.w3c.dom.Node;
18  
19  import sk.stuba.fiit.foo07.genex.beans.Answer;
20  import sk.stuba.fiit.foo07.genex.beans.Picture;
21  import sk.stuba.fiit.foo07.genex.beans.Question;
22  import sk.stuba.fiit.foo07.genex.beans.QuestionType;
23  import sk.stuba.fiit.foo07.genex.common.ResourceHelper;
24  import sk.stuba.fiit.foo07.genex.common.SettingsHelper;
25  import sk.stuba.fiit.foo07.genex.dao.AnswerDao;
26  import sk.stuba.fiit.foo07.genex.dao.AnswerDaoDerby;
27  import sk.stuba.fiit.foo07.genex.dao.PictureDao;
28  import sk.stuba.fiit.foo07.genex.dao.PictureDaoDerby;
29  import sk.stuba.fiit.foo07.genex.dao.QuestionDao;
30  import sk.stuba.fiit.foo07.genex.dao.QuestionDaoDerby;
31  import sk.stuba.fiit.foo07.genex.dao.QuestionTypeDao;
32  import sk.stuba.fiit.foo07.genex.dao.QuestionTypeDaoDerby;
33  import sk.stuba.fiit.foo07.genex.exceptions.ExportException;
34  
35  import com.sun.org.apache.xerces.internal.dom.DocumentImpl;
36  import com.sun.org.apache.xml.internal.serialize.OutputFormat;
37  import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
38  
39  /**
40   * 
41   * @author sochotnicky
42   * 
43   */
44  public class ExportMoodle extends Export {
45  
46      /**
47       * 
48       * @author sochotnicky
49       * 
50       * private aggregate class for more efficient conversion of questions into
51       * HTML
52       */
53      private class QuestionAnswers {
54          public QuestionAnswers(Question q, ArrayList<Answer> ans) {
55              // we need deep copy (blee)
56              this.question = new Question();
57              this.question.setCreated(q.getCreated());
58              this.question.setDerivedFromID(q.getDerivedFromID());
59              this.question.setDifficulty(q.getDifficulty());
60              this.question.setLastUpdate(q.getLastUpdate());
61              this.question.setQuestionID(q.getQuestionID());
62              this.question.setQuestionTypeID(q.getQuestionTypeID());
63              this.question.setText(q.getText());
64              this.question.setUserID(q.getUserID());
65  
66              this.answers = new ArrayList<Answer>(ans.size());
67              for (Answer a : ans) {
68                  Answer newAns = new Answer();
69                  newAns.setAnswerID(a.getAnswerID());
70                  newAns.setIsCorrect(a.getIsCorrect());
71                  newAns.setQuestionID(a.getQuestionID());
72                  newAns.setText(a.getText());
73                  this.answers.add(newAns);
74              }
75          }
76  
77          public Question question;
78          public ArrayList<Answer> answers;
79      }
80  
81      /**
82       * copy of questions to be converted
83       */
84      private ArrayList<QuestionAnswers> questions;
85  
86      private Connection con;
87  
88      private BufferedWriter heveaInput;
89      private BufferedReader heveaOutput;
90      private BufferedReader heveaERR;
91      private Process heveaProc;
92  
93      private static String nl = System.getProperty("line.separator");
94  
95      private StringBuffer heveaOutputData;
96  
97      public ExportMoodle() {
98          super();
99          setOutputFilename("export-moodle.xml");
100     }
101 
102     @Override
103     public void exportQuestions(ArrayList<Question> questions, Connection con)
104             throws IOException, ExportException {
105         try {
106             this.con = con;
107 
108             convertToHTML(questions);
109 
110             Document xmldoc = new DocumentImpl();
111             Node root = xmldoc.createElement("quiz");
112 
113             int num = 1;
114             for (QuestionAnswers q : this.questions) {
115                 Element e = createQuestionXMLElement(q, xmldoc, num++);
116                 root.appendChild(e);
117             }
118 
119             xmldoc.appendChild(root);
120             OutputFormat of = new OutputFormat("XML", "UTF-8", true);
121             of.setIndent(1);
122             of.setIndenting(true);
123 
124             XMLSerializer serializer = new XMLSerializer(new FileOutputStream(
125                     getOutputFilename()), of);
126             // As a DOM Serializer
127             serializer.asDOMSerializer();
128             serializer.serialize(xmldoc.getDocumentElement());
129 
130         } catch (IOException e) {
131             System.err.println("Error running hevea/converting to HTML:" + e);
132             e.printStackTrace();
133             throw e;
134         }
135     }
136 
137     @Override
138     public void exportTest(Integer testId, Connection con) throws IOException,
139             ExportException {
140 
141         QuestionDao qDao = new QuestionDaoDerby(con);
142         //TODO 
143         //pridane odchytavanie vynimky (palo)
144         try {
145             exportQuestions(qDao.getQuestionsByTestID(testId), con);
146         } catch (Exception e) {
147         }
148     }
149 
150     @Override
151     public void exportTests(ArrayList<Integer> testIds, Connection con)
152             throws IOException, ExportException {
153         for (int tid : testIds)
154             exportTest(tid, con);
155     }
156 
157     /**
158      * Converts questions and answers using HeVeA into HTML
159      * 
160      * Questions are separated by '=BEGINQ=' and '=ENDQ='. Also answers are
161      * separated by similar tags.This way we can parse hevea output and store
162      * modified answers
163      * 
164      * @param originalQ
165      *                questions to be converted
166      * @throws IOException
167      */
168     private void convertToHTML(ArrayList<Question> originalQ)
169             throws IOException {
170 
171         heveaOutputData = new StringBuffer();
172         newHeveaProcess();
173         questions = new ArrayList<QuestionAnswers>(originalQ.size());
174 
175         AnswerDao aDao = new AnswerDaoDerby(con);
176         int dataSize = 0;
177         for (int i = 0; i < originalQ.size(); i++) {
178             Question q = originalQ.get(i);
179             if (dataSize >= 10000) {
180                 readHeveaOutput();
181                 dataSize = 0;
182                 newHeveaProcess();
183             }
184 
185             heveaInput.write("=BEGINQ=");
186             heveaInput.write(nl);
187             heveaInput.write("=BEGINTEXT=");
188             heveaInput.write(nl);
189             //heveaInput.write(q.getText().trim());
190             heveaInput.write(q.getText().trim());
191             dataSize += q.getText().length();
192             heveaInput.write(nl);
193             heveaInput.write("=ENDTEXT=");
194             heveaInput.write(nl);
195             //TODO 
196             //pridane odchytavanie vynimky (palo)
197             ArrayList<Answer> answers = null; //pozor mozna chyba
198             try {
199                 answers = aDao.getAnswersByQuestionID(q.getQuestionID());
200             } catch (Exception e) {
201             }
202             questions.add(new QuestionAnswers(q, answers));
203 
204             for (Answer a : answers) {
205                 String text = a.getText();
206                 heveaInput.write("=BEGINANS=");
207                 heveaInput.write(nl);
208                 heveaInput.write(text.trim());
209                 dataSize += text.length();
210                 heveaInput.write(nl);
211                 heveaInput.write("=ENDANS=");
212                 heveaInput.write(nl);
213 
214             }
215 
216             heveaInput.write("=ENDQ=");
217             heveaInput.write(nl);
218 
219             heveaInput.flush();
220         }
221 
222         readHeveaOutput();
223 
224         parseHeveaOutput();
225     }
226 
227     /**
228      * this function reads data from HeVeA output stream (stdout)
229      * 
230      * @throws IOException
231      */
232     private void readHeveaOutput() throws IOException {
233         char c = 0;
234         heveaInput.close();
235         while (heveaERR.ready()) {
236             System.err.print((char) heveaERR.read());
237         }
238 
239         while (heveaOutput.ready()) {
240             c = (char) heveaOutput.read();
241             heveaOutputData.append(c);
242             System.err.print(c);
243         }
244         heveaOutput.close();
245         heveaERR.close();
246     }
247 
248     /**
249      * Creates new HeVeA process and loads default macros
250      * 
251      * @throws IOException
252      */
253     private void newHeveaProcess() throws IOException {
254         try {
255             heveaProc = Runtime.getRuntime().exec(
256                     SettingsHelper.getSetting("hevea_path"));
257             heveaInput = new BufferedWriter(new OutputStreamWriter(heveaProc
258                     .getOutputStream(),"UTF-8"));
259 
260             heveaOutput = new BufferedReader(new InputStreamReader(heveaProc
261                     .getInputStream()));
262 
263             heveaERR = new BufferedReader(new InputStreamReader(heveaProc
264                     .getErrorStream()));
265 
266             ResourceHelper mh = new ResourceHelper();
267             String macros = mh.getMacros();
268 
269             heveaInput.write(macros);
270         } catch (IOException e) {
271             System.err.println("HeVeA execution failed: " + e);
272             throw e;
273         }
274     }
275 
276     /**
277      * parses output of HeVeA into ArrayList<QuestionAnswers> questions
278      *
279      */
280     private void parseHeveaOutput() throws IOException {
281         try {
282             BufferedReader dataSourceReader = new BufferedReader(
283                     new StringReader(heveaOutputData.toString()));
284             String line = null;
285             int converted = 0;
286 
287             while ((line = dataSourceReader.readLine()) != null) {
288                 if ("=BEGINQ=".equals(line)) {
289                     // this reads =BEGINTEXT= from output
290                     String tmpLine = dataSourceReader.readLine();
291                     while (!"=BEGINTEXT=".equals(tmpLine)) {
292                         tmpLine = dataSourceReader.readLine();
293                     }
294 
295                     StringBuffer text = new StringBuffer();
296 
297                     // reads question text 
298                     while (!(tmpLine = dataSourceReader.readLine())
299                             .contains("=ENDTEXT=")) {
300                         text.append(tmpLine);
301                         text.append(nl);
302                     }
303                     /* end of line is =ENDTEXT= */
304                     if (!tmpLine.equals("=ENDTEXT=")) {
305                         text.append(tmpLine.replace("=ENDTEXT=", ""));
306                     }
307 
308                     QuestionAnswers qa = questions.get(converted);
309                     String tmp = text.toString();
310                     if (tmp.contains("DIV CLASS=\"lstlisting\"")) {
311                         tmp = tmp
312                                 .replace(
313                                         "DIV CLASS=\"lstlisting\"",
314                                         "DIV STYLE=\"font-family:monospace;white-space:pre;margin-right:auto;margin-left:0pt;text-align:left\"");
315                         tmp = tmp.replace("/B> <B>", "/B>&nbsp;<B>");
316                         //tmp = tmp.replace("/DIV", "/PRE");
317                     }
318                     qa.question.setText(tmp);
319                     text = null;
320                     int aNum = 0;
321                     // read answers
322                     while (!(tmpLine = dataSourceReader.readLine())
323                             .contains("=ENDQ=")) {
324 
325                         while (!"=BEGINANS=".equals(tmpLine)) {
326                             tmpLine = dataSourceReader.readLine();
327                         }
328                         text = new StringBuffer();
329                         while (!(tmpLine = dataSourceReader.readLine())
330                                 .contains("=ENDANS=")) {
331                             text.append(tmpLine);
332                             text.append(nl);
333                         }
334                         if (!tmpLine.equals("=ENDANS=")) {
335                             text.append(tmpLine.replace("=ENDANS=", ""));
336                         }
337                         tmp = text.toString();
338                         if (tmp.contains("DIV CLASS=\"lstlisting\"")) {
339                             tmp = tmp
340                                     .replace(
341                                             "DIV CLASS=\"lstlisting\"",
342                                             "DIV STYLE=\"font-family:monospace;white-space:pre;margin-right:auto;margin-left:0pt;text-align:left\"");
343                             tmp = tmp.replace("/B> <B>", "/B>&nbsp;<B>");
344                         }
345 
346                         qa.answers.get(aNum++).setText(tmp);
347                         text = null;
348                     }
349 
350                     converted++;
351                 }
352             }
353         } catch (IOException e) {
354             e.printStackTrace();
355             throw new IOException("Error parsing hevea output:" + e);
356         }
357 
358     }
359 
360     /**
361      * Creates XML element from QuestionAnswers variable (one Node in moodle
362      * export)
363      * 
364      * @param qa
365      *                QuestionAnswers to be converted
366      * @param xmldoc
367      *                helper Document for creating elements
368      * @return created xml element
369      */
370     private Element createQuestionXMLElement(QuestionAnswers qa,
371             Document xmldoc, int num) throws ExportException {
372         PictureDao pDao = new PictureDaoDerby(con);
373         // TODO nedokoncena konverzia vsetkych dat
374         QuestionTypeDao qtDao = new QuestionTypeDaoDerby(con);
375         QuestionType qt = null;
376 
377         try {
378             qt = qtDao.getQuestionTypeByQuestionID(qa.question.getQuestionID());
379         } catch (Exception e) {
380         }
381         String strQType = null;
382         if ("single choice".equalsIgnoreCase(qt.getName())
383                 || "multi choice".equalsIgnoreCase(qt.getName())) {
384             strQType = new String("multichoice");
385         } else if ("true/false".equalsIgnoreCase(qt.getName())) {
386             strQType = new String("truefalse");
387         } else if ("fill in".equalsIgnoreCase(qt.getName())) {
388             strQType = new String("shortanswer");
389         } else if ("essay".equalsIgnoreCase(qt.getName())) {
390             strQType = new String("essay");
391         } else {
392             throw new ExportException("Unknown question type " + qt.getName()
393                     + " for moodle export");
394         }
395         Element qElement = xmldoc.createElementNS(null, "question");
396         qElement.setAttribute("type", strQType);
397         Element qNameElement = xmldoc.createElementNS(null, "name");
398         Element textElement = xmldoc.createElementNS(null, "text");
399         textElement.appendChild(xmldoc.createTextNode("Otázka č. " + num));
400         qNameElement.appendChild(textElement);
401         qElement.appendChild(qNameElement);
402 
403         Element qTextElement = xmldoc.createElementNS(null, "questiontext");
404         textElement = xmldoc.createElementNS(null, "text");
405         Node textNode = xmldoc.createTextNode(qa.question.getText());
406 
407         textElement.appendChild(textNode);
408         qTextElement.appendChild(textElement);
409         qElement.appendChild(qTextElement);
410 
411         int correct = 0;
412         for (Answer a : qa.answers) {
413             if (a.getIsCorrect()) {
414                 correct++;
415             }
416         }
417 
418         for (Answer a : qa.answers) {
419             Element aElement = xmldoc.createElementNS(null, "answer");
420             aElement.setAttribute("fraction", a.getIsCorrect() == true ? String
421                     .valueOf(100 / correct) : "0");
422 
423             textElement = xmldoc.createElementNS(null, "text");
424             textNode = xmldoc.createTextNode(a.getText());
425             textElement.appendChild(textNode);
426             aElement.appendChild(textElement);
427             qElement.appendChild(aElement);
428         }
429 
430         ArrayList<Picture> pics = null; //pozor mozna chyba
431         //TODO 
432         //pridane odchytavanie vynimky (palo)
433         try {
434             pics = pDao.getPicturesByQuestionID(qa.question.getQuestionID());
435         } catch (Exception e) {
436         }
437 
438         for (Picture pic : pics) {
439             Element imageElement = xmldoc.createElementNS(null, "image");
440             textNode = xmldoc.createTextNode("genex-import/" + pic.getName());
441             imageElement.appendChild(textNode);
442             qElement.appendChild(imageElement);
443 
444             Element imgDataElement = xmldoc.createElementNS(null,
445                     "image_base64");
446             imgDataElement.appendChild(xmldoc.createTextNode(Base64
447                     .encodeBytes(pic.getContent())));
448 
449             qElement.appendChild(imgDataElement);
450         }
451 
452         if ("multichoice".equalsIgnoreCase(strQType)) {
453             Element singleElement = xmldoc.createElementNS(null, "single");
454             if ("single choice".equalsIgnoreCase(qt.getName())) {
455                 textNode = xmldoc.createTextNode("true");
456             } else {
457                 textNode = xmldoc.createTextNode("false");
458             }
459             singleElement.appendChild(textNode);
460             qElement.appendChild(singleElement);
461         }
462 
463         return qElement;
464     }
465 }