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

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

Second of 2 part commit for improving FormatConversion from GS2 to GS3. formatconverter.exe now takes an additional optional parameter which can be documentNode or classifierNode. This then determines what the formatconverter.exe does when it sees an If test on the existence of the numleafdocs variable, since a positive test applies only to classifierNodes, while a negative test applies only to documentNodes. Further, [link][icon][link] should output something slightly different for classifierNodes than for documentNodes.

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