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

Last change on this file was 36272, checked in by anupama, 23 months ago

Finally tracked down where the commented-out GS3 format statements are coming from. Not gs2build/collect's modelcol, but hardcoded into gli code. Fixing a missing div, which posed a problem for me when I tried uncommenting and editing the format stmt that lacked it.

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 FormatConversionDialog 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 of 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.