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

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

Cosmetic changes: 1. renamed inner StreamGobbler classes. 2. Reverting accidental commit of debugging statements in OpenCollectionDialog.java

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