source: main/trunk/gli/src/org/greenstone/gatherer/gui/FormatConversionDialog.java@ 29009

Last change on this file since 29009 was 29009, checked in by ak19, 10 years ago

Added and now using dictionary strings for GLI's new FormatConversionDialog.

File size: 39.1 KB
Line 
1package org.greenstone.gatherer.gui;
2
3import org.greenstone.gatherer.Configuration;
4import org.greenstone.gatherer.Dictionary;
5import org.greenstone.gatherer.Gatherer;
6//import static org.greenstone.gatherer.cdm.Format4gs3Manager.NumberedJTextArea;
7import org.greenstone.gatherer.cdm.CollectionConfigXMLReadWrite;
8import org.greenstone.gatherer.util.Codec;
9import org.greenstone.gatherer.util.StaticStrings;
10import org.greenstone.gatherer.util.Utility;
11import org.greenstone.gatherer.util.XMLTools;
12
13import org.fife.ui.rsyntaxtextarea.*;
14import org.w3c.dom.*;
15
16import java.io.BufferedReader;
17import java.io.BufferedWriter;
18import java.io.Closeable;
19import java.io.File;
20import java.io.InputStream;
21import java.io.InputStreamReader;
22import java.io.IOException;
23import java.io.OutputStream;
24import java.io.OutputStreamWriter;
25
26//import java.awt.Dimension;
27import java.awt.*;
28import java.awt.event.*;
29import java.util.*;
30import javax.swing.*;
31import javax.swing.border.*;
32import javax.swing.event.*;
33import javax.swing.undo.*;
34
35
36// TO DO:
37// + StreamGobblers: http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html
38// Need to test on Windows: the Ctrl-D/Ctrl-Z sent to formatconverter.exe. May not need to do anything special for Windows now that this code is using StreamGobblers to process the in/out streams of the process
39// Need to use dictionary for labels
40// convertFormat() for remote GS
41// + HTML tidy (put all "<br/>" back to "<br>" in luce/etc/collectionConfig.xml and run again)
42// Help tooltips on buttons
43// + Undo, Redo buttons
44// Split class into dialog/widgets and data processing
45
46public class FormatConversionDialog extends ModalDialog
47{
48 public static final String GSF_FORMAT_GS2_TAG = "gsf:format-gs2";
49 private static final String GSF_GS3_ROOT_TAG = "gsf:gs3-root";
50
51 // The cmdline programs launched by this dialog
52 private static final int XMLTIDY = 0;
53 private static final int FORMATCONVERTER = 1;
54 // Online HTML tidy for learning usage: http://infohound.net/tidy/
55 private static final String[] xmltidy_cmd_args = {"tidy", "-config", Configuration.getGS3BinPath() + "tidyconfig.cfg", "-utf8", "-wrap", "0", "-raw", "-q"}; // {"tidy", "-xml", "-utf8"};
56 private static final String[] formatconverter_cmd_args = {Configuration.getGS3BinPath() + "formatconverter", "--silent"};
57
58 private static final Dimension SIZE = new Dimension(640,480);
59
60 // the important member variables
61 private File collect_cfg_file = null;
62 private Document xml_file_doc = null;
63 private NodeList gsf_format_gs2_list = null;
64
65 private int current_index = -1;
66 private int dlgResult = OpenCollectionDialog.OK_OPTION;
67 private int process_exitValue = -1;
68
69 // widgets
70 final String WARNING_TITLE = Dictionary.get("General.Warning")
71 + ": " + Dictionary.get("FormatConversionDialog.Invalid_XML")
72 + " " + Dictionary.get("FormatConversionDialog.Invalid_XML_Warning_Title");
73
74 private static final Border EMPTYBORDER = new EmptyBorder(5, 5, 5, 5);
75 private JLabel section_label = null;
76 private JButton cancel_button = null;
77 private JButton next_button = null;
78 private JButton accept_all_button = null;
79 private JButton undo_button = null;
80 private JButton redo_button = null;
81 private JButton htmltidy_button = null;
82 private JButton xmltidy_button = null;
83 private NumberedJTextArea gs2_textarea = null;
84 private NumberedJTextArea gs3_textarea = null;
85 private JLabel count_label = null;
86 private JLabel statusbar = null;
87 private UndoManager undoManager;
88
89 public FormatConversionDialog (File collect_cfg_file, Document xml_file_doc, NodeList gsf_format_gs2_list) {
90 super(Gatherer.g_man, "", true);
91
92 this.collect_cfg_file = collect_cfg_file;
93 this.xml_file_doc = xml_file_doc;
94 this.gsf_format_gs2_list = gsf_format_gs2_list;
95
96 this.setComponentOrientation(Dictionary.getOrientation());
97 setSize(SIZE);
98 setTitle(Dictionary.get(Dictionary.get("FormatConversionDialog.Title")));
99 setDefaultCloseOperation(DISPOSE_ON_CLOSE);
100
101
102 JPanel midbutton_panel = new JPanel(); // FlowLayout by default in a JPanel
103 midbutton_panel.setComponentOrientation(Dictionary.getOrientation());
104 JButton reconvert_button = new JButton(Dictionary.get("FormatConversionDialog.Reconvert"));
105 midbutton_panel.add(reconvert_button);
106 reconvert_button.addActionListener(new ReconvertListener());
107 reconvert_button.setAlignmentY(Component.CENTER_ALIGNMENT);
108
109 section_label = new JLabel("Section Label Goes Here"); // title will be replaced, don't need to translate
110 section_label.setBorder(EMPTYBORDER);
111 section_label.setComponentOrientation(Dictionary.getOrientation());
112
113 JPanel button1_panel = new JPanel();
114 button1_panel.setComponentOrientation(Dictionary.getOrientation());
115 undo_button = new JButton(Dictionary.get("General.Undo"));
116 undo_button.addActionListener(new UndoListener());
117 undo_button.setEnabled(false);
118
119 redo_button = new JButton(Dictionary.get("General.Redo"));
120 redo_button.addActionListener(new RedoListener());
121 redo_button.setEnabled(false);
122
123 xmltidy_button = new JButton(Dictionary.get("FormatConversionDialog.XHTML_Tidy"));
124 xmltidy_button.setToolTipText(Dictionary.get("FormatConversionDialog.XHTML_Tidy_Tooltip"));
125 xmltidy_button.addActionListener(new XMLTidyButtonListener());
126
127 button1_panel.add(undo_button);
128 button1_panel.add(redo_button);
129 button1_panel.add(xmltidy_button);
130
131 JPanel button_panel = new JPanel(new FlowLayout());
132 button_panel.setComponentOrientation(Dictionary.getOrientation());
133 count_label = new JLabel("<count>"); // placeholder text for numbers, no need to translate
134 cancel_button = new JButton(Dictionary.get("General.Cancel"));
135 next_button = new JButton(Dictionary.get("FormatConversionDialog.Next"));
136 accept_all_button = new JButton(Dictionary.get("FormatConversionDialog.Accept_All"));
137 cancel_button.addActionListener(new CancelButtonListener());
138 next_button.addActionListener(new NextButtonListener());
139 accept_all_button.addActionListener(new AcceptAllButtonListener());
140 button_panel.add(cancel_button);
141 button_panel.add(next_button);
142 button_panel.add(accept_all_button);
143 button_panel.add(count_label);
144
145
146 // The undoable text areas. Adding the UndoableEditListener has to come after instantiation
147 // of the undo and redo buttons, since the listener expects these buttons to already exist
148 undoManager = new UndoManager();
149
150 gs2_textarea = new NumberedJTextArea();
151 gs3_textarea = new NumberedJTextArea();
152
153 CustomUndoableEditListener customUndoableEditListener = new CustomUndoableEditListener();
154 gs2_textarea.getDocument().addUndoableEditListener(customUndoableEditListener);
155 gs3_textarea.getDocument().addUndoableEditListener(customUndoableEditListener);
156
157 initTextArea(gs2_textarea);
158 initTextArea(gs3_textarea);
159 gs2_textarea.setToolTipText(Dictionary.get("FormatConversionDialog.GS2_Text_Tooltip"));
160 gs3_textarea.setToolTipText(Dictionary.get("FormatConversionDialog.GS2_Text_Tooltip"));
161
162
163 JPanel centre_panel = new JPanel();
164 centre_panel.setLayout(new BoxLayout(centre_panel, BoxLayout.Y_AXIS));
165 centre_panel.setComponentOrientation(Dictionary.getOrientation());
166 centre_panel.setBorder(EMPTYBORDER);
167 centre_panel.add(new JScrollPane(gs2_textarea));
168 centre_panel.add(midbutton_panel);
169 centre_panel.add(new JScrollPane(gs3_textarea));
170
171 JPanel bottom_panel = new JPanel();
172 bottom_panel.setLayout(new BoxLayout(bottom_panel, BoxLayout.Y_AXIS));
173 bottom_panel.setComponentOrientation(Dictionary.getOrientation());
174 bottom_panel.setBorder(EMPTYBORDER);
175
176 bottom_panel.add(button1_panel);
177 bottom_panel.add(button_panel);
178 statusbar = new JLabel("");
179 statusbar.setBorder(EMPTYBORDER);
180 // http://stackoverflow.com/questions/2560784/how-to-center-elements-in-the-boxlayout-using-center-of-the-element
181 statusbar.setAlignmentX(Component.CENTER_ALIGNMENT);
182 bottom_panel.add(statusbar);
183
184 // add all the widgets to the contentpane
185 JPanel content_pane = (JPanel) getContentPane();
186 content_pane.setComponentOrientation(Dictionary.getOrientation());
187 content_pane.setLayout(new BorderLayout());
188 //content_pane.setLayout(new GridLayout(7,1));
189 //content_pane.setLayout(new BoxLayout(content_pane, BoxLayout.Y_AXIS));
190 //content_pane.setBorder(EMPTYBORDER);
191
192 content_pane.add(section_label, BorderLayout.NORTH);
193 content_pane.add(centre_panel, BorderLayout.CENTER);
194 content_pane.add(bottom_panel, BorderLayout.SOUTH);
195
196
197 // Final dialog setup & positioning.
198 getRootPane().setDefaultButton(next_button);
199 Dimension screen_size = Configuration.screen_size;
200 setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
201 screen_size = null;
202 //setVisible(true);
203 }
204
205
206 public static void initTextArea(NumberedJTextArea editor_textarea) {
207 /* Fields specific to RSyntaxQuery inherited class */
208 editor_textarea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);
209 editor_textarea.setBracketMatchingEnabled(true);
210 editor_textarea.setAnimateBracketMatching(true);
211 editor_textarea.setAntiAliasingEnabled(true);
212 editor_textarea.setAutoIndentEnabled(true);
213 editor_textarea.setPaintMarkOccurrencesBorder(false);
214
215 /* Standard fields to JTextArea */
216 editor_textarea.setOpaque(false);
217 editor_textarea.setBackground(Configuration.getColor("coloring.editable_background", false));
218 editor_textarea.setCaretPosition(0);
219 editor_textarea.setLineWrap(true);
220 editor_textarea.setRows(11);
221 editor_textarea.setWrapStyleWord(false);
222 }
223
224 public int getDialogResult() {
225 return dlgResult; // OK_OPTION or CANCEL_OPTION
226 }
227
228 //*************************PROCESSING FUNCTIONS***************************//
229 public static int checkForGS2FormatStatements(File collect_cfg_file) {
230
231 if(Gatherer.GS3 && collect_cfg_file.getAbsolutePath().endsWith(".xml")) {
232 //System.err.println("*** Opening an xml config file");
233
234 Document xml_file_doc = XMLTools.parseXMLFile(collect_cfg_file);
235 Element root = xml_file_doc.getDocumentElement();
236
237 // check if there are any <gsf:format-gs2 /> elements. If there are, then may need to process them
238
239 //NodeList gsf_format_gs2_list = root.getElementsByTagNameNS("gsf", "format-gs2");
240 NodeList gsf_format_gs2_list = root.getElementsByTagName(FormatConversionDialog.GSF_FORMAT_GS2_TAG);
241 if(gsf_format_gs2_list != null && gsf_format_gs2_list.getLength() > 0) {
242
243 // Sample the first of the <gsf:gs2-format/> elements to
244 // check we don't have any CDataSections in the <gsf:gs2-format/> elements
245 // If the first <gsf:gs2-format/> has a CDataSection, it means we've already
246 // converted it to GS3 format statements during an earlier GLI session.
247
248 Node gs2format = gsf_format_gs2_list.item(0);
249 //gs2format.normalize();
250 NodeList children = gs2format.getChildNodes();
251
252 for(int i = 0; i < children.getLength(); i++) {
253 Node child = children.item(i);
254 if(child.getNodeType() == Node.CDATA_SECTION_NODE) {
255 // there are GS2 statements in col config, but they've already been converted to GS3
256 // can open the collection without going through the FormatConversionDialog
257 return OpenCollectionDialog.OK_OPTION;
258 }
259 }
260 System.err.println("*** Found GS2 format statements in config file to be converted to GS3.");
261
262 // If we get here, it means there were no CDataSections in the first (any) <gsf:gs2-format/>
263 // This means it's the first time the GS2 collection is being opened in GLI
264 // Open the FormatCconversionDialog and convert the gs2 statements to gs3:
265 FormatConversionDialog formatconversionDlg = new FormatConversionDialog(collect_cfg_file, xml_file_doc, gsf_format_gs2_list);
266 formatconversionDlg.convertGS2FormatStatements();
267 return formatconversionDlg.getDialogResult();
268
269 }
270 }
271 return OpenCollectionDialog.OK_OPTION; // no GS2 statements in col config, and can open the collection
272 }
273
274
275 /**
276 * runInteractiveProgram() runs a cmdline program that reads from stdinput
277 * until Ctrl-D is encountered. It outputs the result to stdout.
278 *
279 * The cmdline programs HTML Tidy and FormatConverter both behave the same way:
280 * When the formatconverter binary is run in silent mode, it expects input
281 * followed by a newline and then EOF (or if no newline, then 2 EOFs)
282 * which is Ctrl-D on Linux/Mac and Ctrl-Z on Windows.
283 * Then the cmdline program exits by printing the result of the conversion.
284 *
285 * Explicitly sending EOFs from java is no longer necessary, as the SendStreamGobbler
286 * Thread takes care of closing the process stream.
287 *
288 * HTMLTidy returns 0 if no warnings or errors, 1 if just Warnings, 2 if Errors (failure)
289 * http://sourceforge.net/p/tidy/mailman/tidy-develop/thread/[email protected]/
290 *
291 * This code uses the StreamGobbler classes based on
292 * http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
293 */
294 public String runInteractiveProgram(int program, String inputstr) {
295 String outputstr = "";
296 String[] command_args;
297 process_exitValue = -1;
298
299 if(program == XMLTIDY) {
300 command_args = xmltidy_cmd_args;
301 } else if(program == FORMATCONVERTER) {
302 command_args = formatconverter_cmd_args;
303 } else { // unknown command
304 return outputstr;
305 }
306
307 // Generate the formatconverter command
308 /*if (Gatherer.isGsdlRemote) {
309
310 }*/
311
312 try {
313
314 Runtime rt = Runtime.getRuntime();
315 Process prcs = null;
316
317 prcs = rt.exec(command_args);
318
319 // send inputstr to process
320 SendStreamGobbler inputGobbler = new SendStreamGobbler(prcs.getOutputStream(), inputstr);
321
322 // monitor for any error messages
323 ReadStreamGobbler errorGobbler = new ReadStreamGobbler(prcs.getErrorStream(), true);
324
325 // monitor for the expected output line(s)
326 ReadStreamGobbler outputGobbler = new ReadStreamGobbler(prcs.getInputStream());
327
328 // kick them off
329 inputGobbler.start();
330 errorGobbler.start();
331 outputGobbler.start();
332
333 // any error???
334 process_exitValue = prcs.waitFor();
335 //System.out.println("ExitValue: " + exitVal);
336
337 // From the comments of
338 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
339 // To avoid running into nondeterministic failures to get the process output
340 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object:
341 outputGobbler.join();
342 errorGobbler.join();
343 inputGobbler.join();
344
345 outputstr = outputGobbler.getOutput();
346 String errmsg = errorGobbler.getOutput();
347 if(!errmsg.equals("")) {
348 System.err.println("*** Process errorstream: \n" + errmsg + "\n****");
349 }
350
351 } catch(IOException ioe) {
352 System.err.println("IOexception " + ioe.getMessage());
353 //ioe.printStackTrace();
354 } catch(InterruptedException ie) {
355 System.err.println("Process InterruptedException " + ie.getMessage());
356 //ie.printStackTrace();
357 }
358
359 return outputstr;
360 }
361
362
363 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
364 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
365 private void closeResource(Closeable resourceHandle) {
366 try {
367 if(resourceHandle != null) {
368 resourceHandle.close();
369 resourceHandle = null;
370 }
371 } catch(Exception e) {
372 System.err.println("Exception closing resource: " + e.getMessage());
373 e.printStackTrace();
374 }
375 }
376
377 public void convertGS2FormatStatements() {
378
379 // at this point, we know there are one or more <gsf:format-gs2 /> elements
380 // process each of them as follows: unescape, then call formatconverter, then call html tidy on it
381 //NodeList gsf_format_gs2_list = root.getElementsByTagNameNS("gsf", "format-gs2");
382
383 int len = gsf_format_gs2_list.getLength();
384
385 for(int i = 0; i < len; i++) {
386
387 Element gs2format = (Element)gsf_format_gs2_list.item(i);
388 String gs2formatstr = XMLTools.getElementTextValue(gs2format); // seems to already unescape the html entities
389 //gs2formatstr = Codec.transform(gs2formatstr, Codec.ESCAPEDHTML_TO_UNESCAPED);
390
391 processFormatStatement(i, gs2formatstr);
392 }
393
394 increment(); // modifies the textareas to initialise them
395 setVisible(true);
396 }
397
398
399 private String processFormatStatement(int i, String gs2formatstr) {
400 String errorMsg = "";
401
402 boolean startsWithTableCell = (gs2formatstr.toLowerCase().startsWith("<td")) ? true : false;
403
404 //System.err.println("*** Found: " + gs2formatstr);
405
406 String gs3formatstr = runInteractiveProgram(FORMATCONVERTER, gs2formatstr);
407 gs3formatstr = gs3formatstr.replace("> <", "><");
408
409 //System.err.println("*** Format is now: " + gs3formatstr);
410
411 String gs3formatstr_notags = gs3formatstr;
412 gs3formatstr = addSurroundingTags(gs3formatstr);
413
414
415 String validationMsg = XMLTools.parseDOM(gs3formatstr);
416 if(!validationMsg.startsWith(XMLTools.WELLFORMED)) {
417 // Run Html Tidy in XML mode
418 System.err.println("*** Needing to run HTML Tidy on: ");
419 System.err.println(gs3formatstr_notags);
420
421
422 // HTMLTidy returns 0 if no warnings or errors, 1 if just Warnings, 2 if Errors (failure)
423 // http://sourceforge.net/p/tidy/mailman/tidy-develop/thread/[email protected]/
424 String htmltidy_string = runInteractiveProgram(XMLTIDY, gs3formatstr_notags);//removeSurroundingTags(gs3formatstr));
425
426 if(process_exitValue >= 2) {
427 System.err.println("@@@ Process exit value: " + process_exitValue);
428 errorMsg = Dictionary.get("FormatConversionDialog.Tidy_Failed")
429 + " " + Dictionary.get("FormatConversionDialog.XML_Still_Invalid");
430 } else {
431 errorMsg = "";
432
433 gs3formatstr_notags = htmltidy_string;
434
435 gs3formatstr_notags = removeHTMLTags(i, gs3formatstr_notags, startsWithTableCell);
436 gs3formatstr = addSurroundingTags(gs3formatstr_notags);
437
438 // Having applied html-tidy, setGS3Format() will return true if it finally parsed
439 }
440 }
441
442 // For now, assume HTML Tidy has worked and that the gs3format has now been parsed successfully
443
444 boolean parsed = setGS3Format(i, gs3formatstr); // will parse the gs3formatstr into a DOM object
445 if(!parsed && errorMsg.equals("")) {
446 errorMsg = Dictionary.get("FormatConversionDialog.Tidy_Done")
447 + " " + Dictionary.get("FormatConversionDialog.XML_Still_Invalid");
448 }
449 return errorMsg;
450 //return gs3formatstr;
451 }
452
453 // HTML tidy adds entire HTML tags around a single format statement. This method removes it.
454 private String removeHTMLTags(int i, String gs3formatstr_notags, boolean startsWithTD) {
455
456 // if it's a VList classifier <gsf:template match="documentNode|classifierNode"/>
457 // and the gs2 format statement starts with a <td>,
458 // then remove up to and including the outermost <tr>,
459 // else remove up to and including the <body> tag
460
461 String removeOuterTag = "body>";
462 Element parent = (Element)getParentNode(i); // gets parent of GS2format: <gsf:template>
463 if(parent.hasAttribute("match") && (!parent.hasAttribute("mode") || !parent.getAttribute("mode").equals("horizontal"))) {
464 if(startsWithTD) {
465 removeOuterTag = "tr>"; // remove the outermost <tr> cell HTML tidy added around the tablecell
466 }
467 }
468
469 // <unwanted>
470 // <removeThisOuterTag>
471 // lines we want
472 // </removeThisOuterTag>
473 // <unwanted>
474
475 int end = gs3formatstr_notags.indexOf(removeOuterTag);
476 if(end != -1) {
477 gs3formatstr_notags = gs3formatstr_notags.substring(end+removeOuterTag.length());
478 }
479 int start = gs3formatstr_notags.lastIndexOf("</"+removeOuterTag); //closing tag
480 if(start != -1) {
481 gs3formatstr_notags = gs3formatstr_notags.substring(0, start);
482 }
483
484 //System.err.println("@@@@ " + startsWithTD + " - TAG: " + removeOuterTag + " - AFTER REMOVING TAGS:\n" + gs3formatstr_notags);
485
486 return gs3formatstr_notags;
487 }
488
489
490 //**************** ACCESS FUNCTIONS ***************//
491
492 // gs2 format text is the text string that goes into <gsf:format-gs2/>: <gsf:format-gs2>text</gsf:format-gs2>
493 private void setGS2Format(int i, String text) {
494 XMLTools.setElementTextValue(getGS2Format(i), text);
495 }
496
497 // gs3FormatStr represents DOM, and must be parsed and appended as sibling to the gs2format element
498 // as <gsf:gs3-root/>. If it fails to parse, nest it in a CDATA element of <gsf:gs3-root/>
499 private boolean setGS3Format(int i, String gs3formatstr) {
500
501 Document ownerDoc = getGS2Format(i).getOwnerDocument();
502 Node gs3format = null;
503 boolean parse_success = false;
504
505
506 String validationMsg = XMLTools.parseDOM(gs3formatstr);
507 if(!validationMsg.startsWith(XMLTools.WELLFORMED)) {
508 // silently add the gs3formatstr in a CDATA block into the root
509
510 gs3formatstr = removeSurroundingTags(gs3formatstr);
511 gs3format = ownerDoc.createElement(GSF_GS3_ROOT_TAG);
512 Node cdataSection = ownerDoc.createCDATASection(gs3formatstr);
513 gs3format.appendChild(cdataSection);
514 parse_success = false;
515 }
516 else {
517
518 // parse DOM into XML
519 Document doc = XMLTools.getDOM(gs3formatstr);
520 Element root = doc.getDocumentElement();
521 gs3format = ownerDoc.importNode(root, true); // an element
522 parse_success = true;
523
524 // System.err.println("@@@@ ROOT:\n" + XMLTools.xmlNodeToString(root));
525 }
526
527 // add gs3format element as sibling to gs2format element
528 Element oldgs3format = getGS3Format(i);
529 if(oldgs3format == null) {
530 getParentNode(i).appendChild(gs3format);
531 // System.err.println("@@@ APPEND");
532 } else {
533 // replace the existing
534 getParentNode(i).replaceChild(gs3format, oldgs3format);
535 // System.err.println("@@@ REPLACE");
536 }
537
538 //http://stackoverflow.com/questions/12524727/remove-empty-nodes-from-a-xml-recursively
539
540 return parse_success;
541 }
542
543 private Node getParentNode(int i) {
544 return gsf_format_gs2_list.item(i).getParentNode();
545 }
546
547 private Element getGS2Format(int i) {
548 return (Element)gsf_format_gs2_list.item(i);
549 }
550
551 private String getGS2FormatString(int i) {
552 return XMLTools.getElementTextValue(getGS2Format(i));
553 }
554
555 private Element getGS3Format(int i) {
556 Element parent = (Element)getParentNode(i);
557 NodeList nl = parent.getElementsByTagName(GSF_GS3_ROOT_TAG);
558 if(nl.getLength() > 0) {
559 return (Element)nl.item(0);
560 }
561 return null;
562 }
563
564 private String getGS3FormatString(int i) {
565 Element gs3format = getGS3Format(i);
566 if(gs3format == null) {
567 return "";
568 } else {
569
570 // if any child is a CData section, return its text as-is. There will be no indenting
571 NodeList children = gs3format.getChildNodes();
572 for(int j = 0 ; j < children.getLength(); j++) {
573 if(children.item(j).getNodeType() == Node.CDATA_SECTION_NODE) {
574 return children.item(j).getNodeValue(); // content of CDataSection
575 }
576 }
577
578 // else we have proper nodes, return indented string
579 StringBuffer sb = new StringBuffer();
580 XMLTools.xmlNodeToString(sb, gs3format, true, " ", 0);
581 return sb.toString();
582 }
583 }
584
585
586 private String getLabel(int i) {
587 String label = "";
588
589 // Given XML of the form:
590 // <browse|search>
591 // <format>
592 // <gsf:template match="documentNode|classifierNode" [mode=horizontal]>
593 // <gsf-format:gs2 />
594 // <gs3format/>
595 // </format>
596 // </browse|search>
597
598 // Want to return the label: "browse|search > documentNode|classifierNode [> horizontal]"
599
600 Element parent = (Element)getParentNode(i); // gets parent of GS2format: <gsf:template>
601 label = parent.getAttribute("match"); //e.g. documentNode, classifierNode
602
603 if(parent.hasAttribute("mode")) { // e.g. classifierNode mode=horizontal
604 label = label + " > " + parent.getAttribute("mode");
605 }
606
607 Element ancestor = (Element) parent.getParentNode().getParentNode(); // ancestor: <browse>|<search>
608 label = ancestor.getTagName() + " > " + label;
609
610 return label;
611 }
612
613 // gs2 format statements have spaces instead of newlines. For display, replace with newlines
614 private String makeLines(String gs2formatstr) {
615 return gs2formatstr.replaceAll(">\\s+<", ">\n<");
616 }
617
618 private String singleLine(String gs2formatstr) {
619 return gs2formatstr.replace(">\n<", "> <"); // put the spaces back
620 }
621
622 private String removeSurroundingTags(String xml)
623 {
624 //return xml.replace("<"+GSF_GS3_ROOT_TAG+" xmlns:gsf=\"http://www.greenstone.org/greenstone3/schema/ConfigFormat\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n", "").replace("\n</"+GSF_GS3_ROOT_TAG+">", "");//.trim();
625 return xml.replaceAll("<"+GSF_GS3_ROOT_TAG+" xmlns:gsf=(\"|\')http://www.greenstone.org/greenstone3/schema/ConfigFormat(\"|\') xmlns:xsl=(\"|\')http://www.w3.org/1999/XSL/Transform(\"|\')>\n?", "").replaceAll("\n?</"+GSF_GS3_ROOT_TAG+">", "");//.trim();
626 }
627
628 private String addSurroundingTags(String gs3formatstr) {
629 return "<"+GSF_GS3_ROOT_TAG+" xmlns:gsf='http://www.greenstone.org/greenstone3/schema/ConfigFormat' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>"+gs3formatstr+"</"+GSF_GS3_ROOT_TAG+">";
630 }
631
632 //*************************FUNCTIONS THAT INTERACT WITH WIDGETS************************//
633 // increment() loads the next values into the dialog
634 private boolean increment() {
635 current_index++;
636 section_label.setText ( getLabel(current_index) );
637 gs2_textarea.setText( makeLines(getGS2FormatString(current_index)) );
638 gs3_textarea.setText( removeSurroundingTags(getGS3FormatString(current_index)) );
639 statusbar.setText("");
640
641 // as we're on a new screen of dialog, need to clear all undo/redo history
642 undoManager.discardAllEdits();
643 undo_button.setEnabled(false);
644 redo_button.setEnabled(false);
645
646 int len = gsf_format_gs2_list.getLength();
647 count_label.setText((current_index+1) + " / " + len);
648 if((current_index+1) == len) {
649 return false;
650 } else {
651 return true;
652 }
653 }
654
655
656 private void setStatus(String msg) {
657 statusbar.setText(msg);
658 }
659 private void setErrorStatus(String msg) {
660 statusbar.setBackground(Color.red);
661 statusbar.setText(msg);
662 }
663
664
665 public void dispose() {
666 //System.err.println("@@@@ DIALOG CLOSING!");
667 if(dlgResult != OpenCollectionDialog.CANCEL_OPTION) {
668 // Need to remove the <gsf:gs3-root/> siblings of all <gsf:format-gs2/>
669 // Then, get the children of each <gsf:gs3-root/> and add these as siblings of <gsf:format-gs2/>
670
671
672 int len = gsf_format_gs2_list.getLength();
673 for(int k=len-1; k >= 0; k--) {
674 Element parent = (Element)getParentNode(k);
675
676 //parent.normalize();
677
678 NodeList children = parent.getChildNodes();
679
680 // now have to loop/remove nodes in reverse order, since the following loop
681 // modifies the very nodelist we're looping over by removing nodes from it
682
683 int numChildren = children.getLength()-1;
684
685 for(int i=numChildren; i >= 0; i--) {
686 //for(int i = 0; i < children.getLength(); i++) {
687 Node child = children.item(i);
688
689 if(child.getNodeName().equals(GSF_FORMAT_GS2_TAG)) {
690 // if we're dealing with gs2-format-stmts, put their textnode contents in CData sections
691 // http://www.w3schools.com/xml/xml_cdata.asp
692 // This avoids having to look at html-encoded gs2-format tags in the Format pane
693
694 Element gs2format = (Element)child;
695 String gs2formatstr = XMLTools.getElementTextValue(gs2format);
696 gs2formatstr = Codec.transform(gs2formatstr, Codec.ESCAPEDHTML_TO_UNESCAPED);
697
698 Node textNode = XMLTools.getNodeTextNode(gs2format);
699 Node cdataSection = gs2format.getOwnerDocument().createCDATASection(gs2formatstr);
700 gs2format.replaceChild(cdataSection, textNode);
701 }
702 else if(child.getNodeName().equals(GSF_GS3_ROOT_TAG)) {
703
704 // remove GS3 node and append its children to the parent in its place
705 // the <gsf:gs3-root /> elements wouldn't be in the xml_file_doc DOM tree
706 // unless they were valid XML, so don't need to check for validity here
707
708 Node gs3root = child;
709 NodeList gs3_format_lines = gs3root.getChildNodes();
710
711 for(int j = 0; j < gs3_format_lines.getLength(); j++) {
712 Node duplicate = gs3_format_lines.item(j).cloneNode(true);
713 parent.appendChild(duplicate);
714 }
715 gs3root = parent.removeChild(gs3root);
716 gs3root = null; // finished processing
717
718 } // else - skip all nodes other than <gsf:format-gs2/> and <gsf:gs3-root/>
719 }
720 }
721
722 Element root = xml_file_doc.getDocumentElement();
723 //System.err.println("### XML file to write out:\n" + XMLTools.xmlNodeToString(root));
724
725 // Finally, write out the collection xml file
726 String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
727 XMLTools.writeXMLFile(collect_cfg_file, xml_file_doc, nonEscapingTagNames);
728
729 }
730 super.dispose();
731
732 }
733
734 //******************INNER CLASSES including LISTENERS and STREAMGOBBLERS****************//
735
736 /**
737 * A textarea with the line number next to each line of the text
738 */
739 public class NumberedJTextArea extends RSyntaxTextArea /* JTextArea */
740 {
741 public void paintComponent(Graphics g)
742 {
743 Insets insets = getInsets();
744 Rectangle rectangle = g.getClipBounds();
745 g.setColor(Color.white);
746 g.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
747
748 super.paintComponent(g);
749
750 if (rectangle.x < insets.left)
751 {
752 FontMetrics font_metrics = g.getFontMetrics();
753 int font_height = font_metrics.getHeight();
754 int y = font_metrics.getAscent() + insets.top;
755 int line_number_start_point = ((rectangle.y + insets.top) / font_height) + 1;
756 if (y < rectangle.y)
757 {
758 y = line_number_start_point * font_height - (font_height - font_metrics.getAscent());
759 }
760 int y_axis_end_point = y + rectangle.height + font_height;
761 int x_axis_start_point = insets.left;
762 x_axis_start_point -= getFontMetrics(getFont()).stringWidth(Math.max(getRows(), getLineCount() + 1) + " ");
763 if (!this.getText().trim().equals(""))
764 {
765 g.setColor(Color.DARK_GRAY);
766 }
767 else
768 {
769 g.setColor(Color.white);
770 }
771 int length = ("" + Math.max(getRows(), getLineCount() + 1)).length();
772 while (y < y_axis_end_point)
773 {
774 g.drawString(line_number_start_point + " ", x_axis_start_point, y);
775 y += font_height;
776 line_number_start_point++;
777 }
778 }
779 }
780
781
782 public Insets getInsets()
783 {
784 Insets insets = super.getInsets(new Insets(0, 0, 0, 0));
785 insets.left += getFontMetrics(getFont()).stringWidth(Math.max(getRows(), getLineCount() + 1) + " ");
786 return insets;
787 }
788 }
789
790 private class ReconvertListener implements ActionListener {
791 public void actionPerformed(ActionEvent e) {
792 String gs2formatstr = singleLine(gs2_textarea.getText());
793 String errorMsg = processFormatStatement(current_index, gs2formatstr);
794 gs3_textarea.setText( removeSurroundingTags(getGS3FormatString(current_index)) );
795 if(!errorMsg.equals("")) {
796 setErrorStatus(errorMsg);
797 } else {
798 statusbar.setText("");
799 }
800 }
801 }
802
803
804 private class NextButtonListener implements ActionListener {
805 public void actionPerformed(ActionEvent e) {
806 //statusbar.setText("");
807
808 // check if the GS3 format statement is valid XML before storing. If not, let user to decide
809 // whether they want to re-edit it or if they want to keep it as-is, in which case it needs
810 // to be stored as CDATA, which will make it an inactive format statement.
811 // See http://www.w3schools.com/xml/xml_cdata.asp
812 // setGS3Format() already stores invalidXML as CDATA.
813
814 // Check if the GS3 format statement is valid XML before storing. If not, let the
815 // user to decide whether they want to re-edit it or store it as-is and continue
816
817 // user okay-ed the lines currently displayed, store them
818 setGS2Format( current_index, singleLine(gs2_textarea.getText()) );
819 boolean parse_success = setGS3Format( current_index, addSurroundingTags(gs3_textarea.getText()) );
820
821 if(!parse_success) { // invalid XML, warn the user
822 String message = Dictionary.get("FormatConversionDialog.Invalid_XML") + " " + Dictionary.get("FormatConversionDialog.Cancel_Or_Continue_Next");
823 int user_choice = JOptionPane.showConfirmDialog(FormatConversionDialog.this, message, WARNING_TITLE, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
824
825 if(user_choice == JOptionPane.CANCEL_OPTION) {
826 return; // do nothing on this NextButton press. Don't increment. Let user re-adjust invalid XML for GS3 statement.
827 }
828 }
829
830 if(increment()) {
831 repaint();
832 } else {
833 next_button.setEnabled(false);
834 getRootPane().setDefaultButton(accept_all_button);
835
836 }
837
838 }
839 }
840
841 private class CancelButtonListener implements ActionListener {
842 public void actionPerformed(ActionEvent e) {
843 dlgResult = OpenCollectionDialog.CANCEL_OPTION;
844 FormatConversionDialog.this.dispose(); // close dialog
845 }
846 }
847
848
849 private class AcceptAllButtonListener implements ActionListener {
850 public void actionPerformed(ActionEvent e) {
851 //statusbar.setText("");
852
853 // user okay-ed the lines, store them
854 setGS2Format(current_index, gs2_textarea.getText());
855 String gs3formatstr = gs3_textarea.getText();
856 boolean parse_success = setGS3Format(current_index, addSurroundingTags(gs3formatstr));
857 String message = "";
858
859 if(!parse_success) { // invalid XML for current format statement, warn the user
860 setErrorStatus(Dictionary.get("FormatConversionDialog.Invalid_XML"));
861
862 message = Dictionary.get("FormatConversionDialog.Invalid_XML") + " " + Dictionary.get("FormatConversionDialog.Cancel_Or_Continue_Next");
863 }
864
865 // Even if the current GS3 format statement is valid XML, the user could have pressed
866 // Accept All at the very start of the FormatConversion dialog too. Check all the
867 // subsequent format statements, and if any have invalid XML, warn user.
868 for(int i = current_index+1; parse_success && i < gsf_format_gs2_list.getLength(); i++) {
869 gs3formatstr = getGS3FormatString(i);
870 String validationMsg = XMLTools.parseDOM(gs3formatstr);
871 if(!validationMsg.startsWith(XMLTools.WELLFORMED)) {
872 parse_success = false;
873 message = Dictionary.get("FormatConversionDialog.Cancel_Or_Accept_All");
874 }
875 }
876
877 if(!parse_success) {
878 int user_choice = JOptionPane.showConfirmDialog(FormatConversionDialog.this, message, WARNING_TITLE, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
879 if(user_choice == JOptionPane.CANCEL_OPTION) {
880 return; // Don't close the dialog. Let the user continue looking at this or subsequent GS3 format statements.
881 }
882 }
883
884 // If we're here, then either the format statements parsed, or the user accepted them anyway
885 FormatConversionDialog.this.dispose(); // close dialog
886 }
887 }
888
889 private class XMLTidyButtonListener implements ActionListener {
890 // run HTML tidy taking input from stdin
891 // http://www.w3.org/People/Raggett/tidy/
892 public void actionPerformed(ActionEvent e) {
893 String gs3formatstr_notags = gs3_textarea.getText();
894
895 // Flag to determine which tags that HTML tidy adds need to be removed
896 boolean startsWithTableCell = (gs3formatstr_notags.trim().toLowerCase().startsWith("<td")) ? true : false;
897 // run HTML tidy on the GS3 format statement
898 String htmltidy_string = runInteractiveProgram(XMLTIDY, gs3formatstr_notags);
899
900 if(process_exitValue >= 2) {
901 System.err.println("@@@ Process exit value: " + process_exitValue);
902 setErrorStatus(Dictionary.get("FormatConversionDialog.Tidy_Failed"));
903 } else {
904 gs3formatstr_notags = htmltidy_string;
905
906 // HTML tidy adds extra HTML markup around the formatstring, so will need to remove it:
907 gs3formatstr_notags = removeHTMLTags(current_index, gs3formatstr_notags, startsWithTableCell);
908
909 // put the XML tags around the gs3 format string again so we can convert it to a DOM object
910 String gs3formatstr = addSurroundingTags(gs3formatstr_notags);
911
912 boolean parse_success = setGS3Format(current_index, gs3formatstr); // converts to DOM object
913
914 // Get indented GS3 format string from DOM object and display it in the text area
915 gs3_textarea.setText( removeSurroundingTags(getGS3FormatString(current_index)) );
916
917 if(parse_success) {
918 statusbar.setText(Dictionary.get("FormatConversionDialog.Tidy_Done"));
919 } else {
920 setErrorStatus(Dictionary.get("FormatConversionDialog.Tidy_Done")
921 + " " + Dictionary.get("FormatConversionDialog.Error_GS3_Format")
922 + " " + Dictionary.get("FormatConversionDialog.Invalid_XML"));
923 }
924 }
925 }
926 }
927
928 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
929 class ReadStreamGobbler extends Thread
930 {
931 InputStream is = null;
932 StringBuffer outputstr = new StringBuffer();
933 boolean split_newlines = false;
934
935
936 public ReadStreamGobbler(InputStream is)
937 {
938 this.is = is;
939 split_newlines = false;
940 }
941
942 public ReadStreamGobbler(InputStream is, boolean split_newlines)
943 {
944 this.is = is;
945 this.split_newlines = split_newlines;
946 }
947
948 public void run()
949 {
950 BufferedReader br = null;
951 try {
952 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
953 String line=null;
954 while ( (line = br.readLine()) != null) {
955 //System.out.println("@@@ GOT LINE: " + line);
956 outputstr.append(line);
957 if(split_newlines) {
958 outputstr.append("\n");
959 }
960
961 }
962 } catch (IOException ioe) {
963 ioe.printStackTrace();
964 } finally {
965 closeResource(br);
966 }
967 }
968
969 public String getOutput() {
970 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
971 }
972 }
973
974 class SendStreamGobbler extends Thread
975 {
976 OutputStream os = null;
977 String inputstr = "";
978
979 public SendStreamGobbler(OutputStream os, String inputstr)
980 {
981 this.os = os;
982 this.inputstr = inputstr;
983 }
984
985 public void run()
986 {
987 BufferedWriter osw = null;
988 try {
989 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
990 //System.out.println("@@@ SENDING LINE: " + inputstr);
991 osw.write(inputstr, 0, inputstr.length());
992 osw.newLine();//osw.write("\n");
993 osw.flush();
994
995 // Don't explicitly send EOF when using StreamGobblers as below,
996 // as the EOF char is echoed to output.
997 // Flushing the write handle and/or closing the resource seems
998 // to already send EOF silently.
999
1000 /*if(Utility.isWindows()) {
1001 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
1002 } else { // EOF on Linux/Mac is Ctrl-D
1003 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
1004 }
1005 osw.flush();
1006 */
1007 } catch (IOException ioe) {
1008 ioe.printStackTrace();
1009 } finally {
1010 closeResource(osw);
1011 }
1012 }
1013 }
1014
1015 private class UndoListener implements ActionListener
1016 {
1017 public void actionPerformed(ActionEvent event)
1018 {
1019 try {
1020 if (undoManager.canUndo()) {
1021 redo_button.setEnabled(true);
1022 undoManager.undo();
1023 }
1024
1025 if (!undoManager.canUndo()) {
1026 undo_button.setEnabled(false);
1027 } else {
1028 undo_button.setEnabled(true);
1029 }
1030
1031 } catch (Exception e) {
1032 System.err.println("Exception trying to undo: " + e.getMessage());
1033 }
1034 }
1035 }
1036
1037 private class RedoListener implements ActionListener
1038 {
1039 public void actionPerformed(ActionEvent evt)
1040 {
1041 try {
1042 if (undoManager.canRedo()) {
1043 undo_button.setEnabled(true); // the difference with Format4gs3Manager, and no DocumentListener
1044 undoManager.redo();
1045 }
1046
1047 if (!undoManager.canRedo()) {
1048 redo_button.setEnabled(false);
1049 } else {
1050 redo_button.setEnabled(true);
1051 }
1052
1053 } catch (Exception e) {
1054 System.err.println("Exception trying to redo: " + e.getMessage());
1055 }
1056 }
1057 }
1058
1059 private class CustomUndoableEditListener implements UndoableEditListener {
1060 public void undoableEditHappened(UndoableEditEvent evt)
1061 {
1062
1063 undoManager.addEdit(evt.getEdit());
1064 redo_button.setEnabled(false);
1065 undo_button.setEnabled(true);
1066 }
1067 }
1068
1069}
Note: See TracBrowser for help on using the repository browser.