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

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

Correction to recent commit in OpenCollectionDialog. And cosmetic changes to FormatConversionDialog and XMLTools

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