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

Last change on this file since 31582 was 31582, checked in by ak19, 7 years ago

In place of the Input- and OutputStreamGobbler classes, am now shifting GLI code to use SafeProcess too, copied across from GS3 src code. Class SafeProcess includes the two streamgobblers as static inner classes and some more functionality with safely running an external process from Java.

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