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

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