source: other-projects/FileTransfer-WebSocketPair/testGXTWithGreenstone/src/org/greenstone/gatherer/gui/FormatConversionDialog.java@ 33053

Last change on this file since 33053 was 33053, checked in by ak19, 5 years ago

I still had some stuff of Nathan Kelly's (FileTransfer-WebSocketPair) sitting on my USB. Had already commited the Themes folder at the time, 2 years back. Not sure if he wanted this additional folder commited. But I didn't want to delete it and decided it will be better off on SVN. When we use his project, if we find we didn't need this test folder, we can remove it from svn then.

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