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

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

Implemented Undo and Redo. Moved a functions around to better organise in sensible groups.

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