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

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

Should use GLIButton instead of regular JButton to be consistent with the rest of GLI and inherit common behaviour

File size: 40.4 KB
RevLine 
[28995]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
[29006]26//import java.awt.Dimension;
[28995]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
[29006]41// + HTML tidy (put all "<br/>" back to "<br>" in luce/etc/collectionConfig.xml and run again)
[28995]42// Help tooltips on buttons
[29006]43// + Undo, Redo buttons
[29011]44// X Split class into dialog/widgets and data processing?
45// http://www.java2s.com/Tutorial/Java/0240__Swing/SettingJOptionPanebuttonlabelstoFrench.htm
[28995]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
[29009]71 final String WARNING_TITLE = Dictionary.get("General.Warning")
72 + ": " + Dictionary.get("FormatConversionDialog.Invalid_XML")
73 + " " + Dictionary.get("FormatConversionDialog.Invalid_XML_Warning_Title");
74
[28995]75 private static final Border EMPTYBORDER = new EmptyBorder(5, 5, 5, 5);
76 private JLabel section_label = null;
[29012]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;
[28995]84 private NumberedJTextArea gs2_textarea = null;
85 private NumberedJTextArea gs3_textarea = null;
86 private JLabel count_label = null;
87 private JLabel statusbar = null;
[29006]88 private UndoManager undoManager;
[28995]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);
[29009]99 setTitle(Dictionary.get(Dictionary.get("FormatConversionDialog.Title")));
[29011]100 this.addWindowListener(new WindowClosingListener()); // the dialog's top right close button should behave as a Cancel press
[28995]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());
[29012]106 JButton reconvert_button = new GLIButton(Dictionary.get("FormatConversionDialog.Reconvert"), Dictionary.get("FormatConversionDialog.Reconvert_Tooltip"));
[28995]107 midbutton_panel.add(reconvert_button);
108 reconvert_button.addActionListener(new ReconvertListener());
109 reconvert_button.setAlignmentY(Component.CENTER_ALIGNMENT);
110
[29009]111 section_label = new JLabel("Section Label Goes Here"); // title will be replaced, don't need to translate
[28995]112 section_label.setBorder(EMPTYBORDER);
113 section_label.setComponentOrientation(Dictionary.getOrientation());
114
115 JPanel button1_panel = new JPanel();
116 button1_panel.setComponentOrientation(Dictionary.getOrientation());
[29012]117 undo_button = new GLIButton(Dictionary.get("General.Undo"), Dictionary.get("General.Undo_Tooltip"));
[29006]118 undo_button.addActionListener(new UndoListener());
119 undo_button.setEnabled(false);
120
[29012]121 redo_button = new GLIButton(Dictionary.get("General.Redo"), Dictionary.get("General.Redo_Tooltip"));
[29006]122 redo_button.addActionListener(new RedoListener());
123 redo_button.setEnabled(false);
124
[29012]125 xmltidy_button = new GLIButton(Dictionary.get("FormatConversionDialog.XHTML_Tidy"), Dictionary.get("FormatConversionDialog.XHTML_Tidy_Tooltip"));
[28995]126 xmltidy_button.addActionListener(new XMLTidyButtonListener());
[29006]127
128 button1_panel.add(undo_button);
129 button1_panel.add(redo_button);
[28995]130 button1_panel.add(xmltidy_button);
131
132 JPanel button_panel = new JPanel(new FlowLayout());
133 button_panel.setComponentOrientation(Dictionary.getOrientation());
[29009]134 count_label = new JLabel("<count>"); // placeholder text for numbers, no need to translate
[29012]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"));
[28995]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
[29006]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);
[29009]160 gs2_textarea.setToolTipText(Dictionary.get("FormatConversionDialog.GS2_Text_Tooltip"));
[29011]161 gs3_textarea.setToolTipText(Dictionary.get("FormatConversionDialog.GS3_Text_Tooltip"));
[29006]162
163
[28995]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);
[29009]222 editor_textarea.setWrapStyleWord(false);
[28995]223 }
224
[29006]225 public int getDialogResult() {
226 return dlgResult; // OK_OPTION or CANCEL_OPTION
227 }
[28995]228
[29006]229 //*************************PROCESSING FUNCTIONS***************************//
[28995]230 public static int checkForGS2FormatStatements(File collect_cfg_file) {
231
232 if(Gatherer.GS3 && collect_cfg_file.getAbsolutePath().endsWith(".xml")) {
[29011]233
[28995]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) {
[29001]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
[28995]259 return OpenCollectionDialog.OK_OPTION;
260 }
261 }
262 System.err.println("*** Found GS2 format statements in config file to be converted to GS3.");
263
[29011]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
[28995]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 SendStreamGobbler
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 SendStreamGobbler inputGobbler = new SendStreamGobbler(prcs.getOutputStream(), inputstr);
333
334 // monitor for any error messages
335 ReadStreamGobbler errorGobbler = new ReadStreamGobbler(prcs.getErrorStream(), true);
336
337 // monitor for the expected output line(s)
338 ReadStreamGobbler outputGobbler = new ReadStreamGobbler(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
[29006]406 increment(); // modifies the textareas to initialise them
[28995]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);
[29009]440 errorMsg = Dictionary.get("FormatConversionDialog.Tidy_Failed")
441 + " " + Dictionary.get("FormatConversionDialog.XML_Still_Invalid");
[28995]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("")) {
[29009]458 errorMsg = Dictionary.get("FormatConversionDialog.Tidy_Done")
459 + " " + Dictionary.get("FormatConversionDialog.XML_Still_Invalid");
[28995]460 }
461 return errorMsg;
462 //return gs3formatstr;
463 }
464
[29006]465 // HTML tidy adds entire HTML tags around a single format statement. This method removes it.
[28995]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
[29006]502 //**************** ACCESS FUNCTIONS ***************//
[28995]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
[29006]644 //*************************FUNCTIONS THAT INTERACT WITH WIDGETS************************//
[28995]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
[29006]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
[28995]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
[29006]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
[28995]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
[29011]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
[28995]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
[29009]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);
[28995]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
[29009]880 setErrorStatus(Dictionary.get("FormatConversionDialog.Invalid_XML"));
[28995]881
[29009]882 message = Dictionary.get("FormatConversionDialog.Invalid_XML") + " " + Dictionary.get("FormatConversionDialog.Cancel_Or_Continue_Next");
[28995]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;
[29009]893 message = Dictionary.get("FormatConversionDialog.Cancel_Or_Accept_All");
[28995]894 }
895 }
896
897 if(!parse_success) {
[29009]898 int user_choice = JOptionPane.showConfirmDialog(FormatConversionDialog.this, message, WARNING_TITLE, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
[28995]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);
[29009]922 setErrorStatus(Dictionary.get("FormatConversionDialog.Tidy_Failed"));
[28995]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) {
[29009]938 statusbar.setText(Dictionary.get("FormatConversionDialog.Tidy_Done"));
[28995]939 } else {
[29009]940 setErrorStatus(Dictionary.get("FormatConversionDialog.Tidy_Done")
941 + " " + Dictionary.get("FormatConversionDialog.Error_GS3_Format")
942 + " " + Dictionary.get("FormatConversionDialog.Invalid_XML"));
[28995]943 }
944 }
945 }
946 }
947
948 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
949 class ReadStreamGobbler extends Thread
950 {
951 InputStream is = null;
[29006]952 StringBuffer outputstr = new StringBuffer();
[28995]953 boolean split_newlines = false;
954
955
956 public ReadStreamGobbler(InputStream is)
957 {
958 this.is = is;
959 split_newlines = false;
960 }
961
962 public ReadStreamGobbler(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);
[29006]976 outputstr.append(line);
[28995]977 if(split_newlines) {
[29006]978 outputstr.append("\n");
[28995]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 SendStreamGobbler extends Thread
995 {
996 OutputStream os = null;
997 String inputstr = "";
998
999 public SendStreamGobbler(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
[29006]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 }
[28995]1055 }
[29006]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 }
[28995]1077 }
[29006]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.