root/cdat/trunk/las/addXML/addXML.java

Revision 5600, 45.6 kB (checked in by mlaker1, 3 years ago)

Added server authentication.

Line 
1 package gov.noaa.pmel.tmap.addxml;
2
3 /**
4  * @todo add an institution tag for dods servers.  Bugzilla #272
5  */
6
7 /**
8  * @todo If a THREDDS <dataset> element has xlink documentation it's being lost.
9  * Need to figure out how to move this information up into the LAS category that
10  * contains the data set.
11  */
12
13 /**
14  * @todo Ever since rethinking the design (a lofty word for the process of
15  * deciding what this code would look like) the main code
16  * is pretty messy with the logic to combine netCDF data sets into one.
17  * Creating a "full" <las_data> docuement and then extracting the pieces
18  * seems silly.
19  */
20
21 /**
22  * @todo There seem to be a number of undue kludges in the time handling.
23  * I think this should be cleaner, but then again I think most of the time
24  * axis information the kludges are supporting should just get rejected out
25  * of hand.  We can't do everything.  (Some cover an FDS bug that seems to
26  * split the time units into things like:
27  * units="days"
28  * time_origin="1990-MAY-19 00:00")
29  */
30
31 /**
32  * @todo  Add a flag that will cause a <category> element to be created
33  * for each LAS <dataset> created from a -n source so that netCDF and THREDDS
34  * addXML output can be used together without modification.
35  */
36
37 // Standard Java stuff.
38 import java.io.*;
39 import java.io.FileWriter;
40 import java.security.*;
41 import java.text.*;
42 import java.util.*;
43 import java.util.Iterator;
44
45 // www.jdom.org
46 import org.jdom.*;
47 import org.jdom.input.*;
48 import org.jdom.output.*;
49 //joda-time.sourceforge.net/
50 import org.joda.time.*;
51 import org.joda.time.format.*;
52 import com.martiansoftware.jsap.*;
53 // Unidata
54 import thredds.catalog.*;
55 import ucar.nc2.Attribute;
56 import ucar.nc2.dataset.*;
57 import ucar.nc2.dataset.grid.*;
58 import ucar.nc2.dods.*;
59 import ucar.nc2.units.*;
60 import ucar.unidata.geoloc.*;
61 import ucar.unidata.geoloc.projection.*;
62
63 /**
64  * <p>Title: addXML</p>
65  *
66  * <p>Description: Reads local or OPeNDAP netCDF files and generates LAS XML
67  * configuration information.</p>
68  *
69  * <p>Copyright: Copyright (c) 2005</p>
70  *
71  * <p>Company: NOAA/PMEL/TMAP</p>
72  *
73  * @author RHS
74  * @version 1.0
75  */
76
77 import java.net.Authenticator;
78 import java.net.PasswordAuthentication;
79 class TheAuthenticator extends Authenticator {
80     private String username;
81     private String password;
82     public TheAuthenticator(String user, String pwd) {
83         username = user;
84         password = pwd;
85     }
86     protected PasswordAuthentication getPasswordAuthentication() {
87         return new PasswordAuthentication(username, password.toCharArray());
88     }
89 }
90
91 public class addXML {
92
93   private static boolean verbose;
94   private static int fileCount;
95   private static HashMap forceAxes = new HashMap();
96   private static String title;
97   private static String version_string = "1.0.0.0";
98   private static String global_title_attribute;
99   private static boolean category;
100   private static boolean use_suffix = false;
101
102   public addXML() {
103     try {
104       jbInit();
105     }
106     catch (Exception ex) {
107       ex.printStackTrace();
108     }
109   }
110
111   public static void main(String[] args) {
112
113     forceAxes.put("x", new Boolean(false));
114     forceAxes.put("y", new Boolean(false));
115     forceAxes.put("z", new Boolean(false));
116     forceAxes.put("t", new Boolean(false));
117
118     LAS_JSAP command_parser = new LAS_JSAP();
119
120     JSAPResult command_parameters = command_parser.parse(args);
121
122     if (!command_parameters.success()) {
123       command_parser.errorout(command_parameters);
124     }
125
126     String[] data = command_parameters.getStringArray("in_netcdf");
127     String[] thredds = command_parameters.getStringArray("in_thredds");
128     String in_xml = command_parameters.getString("in_xml");
129     String basename = command_parameters.getString("basename");
130     global_title_attribute = command_parameters.getString(
131         "title_attribute");
132     boolean oneDataset = command_parameters.getBoolean("dataset");
133     title = command_parameters.getString("dataset");
134     category = command_parameters.getBoolean("category");
135     String username = command_parameters.getString("username");
136     String password = command_parameters.getString("password");
137
138     boolean forceArange = command_parameters.getBoolean("arange");
139     String aranges;
140     if (forceArange) {
141       String ax[] = command_parameters.getStringArray("arange");
142       for (int a = 0; a < ax.length; a++) {
143         if (ax[a].equals("x") || ax[a].equals("y") ||
144             ax[a].equals("z") || ax[a].equals("t")) {
145           forceAxes.put(ax[a], new Boolean(true));
146         }
147         else {
148           System.err.println("Ignoring axis " + ax[a] +
149                              " on the --arange option. Unknown axis.  Must be x,y,z or t.");
150         }
151       }
152     }
153
154     verbose = command_parameters.getBoolean("verbose");
155     boolean version = command_parameters.getBoolean("version");
156
157     int total = data.length + thredds.length;
158     if (!oneDataset && total > 1) {
159       use_suffix = true;
160     }
161     else if (oneDataset && thredds.length >= 1) {
162       use_suffix = true;
163     }
164
165     // If there will only be one dataset for all netCDF arguments
166     // accumulate them in here.
167     DatasetsGridsAxesBean oneDgab = new DatasetsGridsAxesBean();
168     DatasetBean oneDb = new DatasetBean();
169     // If we are creating categories and we are creating only
170     // one dataset for the netCDF data, we'll use this category bean.
171     CategoryBean oneCat = new CategoryBean();
172
173     oneDb.setCreator(addXML.class.getName());
174     oneDb.setVersion(version_string);
175     // Every variable in this dataset object will have it's own fully qualified
176     // data URL.  Set the data set URL to null.
177     oneDb.setUrl(null);
178     if (title != null && title != "") {
179       oneDb.setName(title);
180       oneCat.setName(title);
181     }
182
183     if (version) {
184       System.out.println("Version: " + version_string);
185     }
186
187     java.net.Authenticator.setDefault(new TheAuthenticator(username, password));
188
189     if (data.length == 0 && thredds.length == 0) {
190       System.err.println("");
191       System.err.println("You must specify either");
192       System.err.println("\ta THREDDS catalog with the -t option or ");
193       System.err.println("\ta netCDF data source with the -n option.");
194       System.err.println("");
195       System.err.print("Usage: addXML.sh ");
196       System.err.print(command_parser.getUsage());
197       System.err.println("");
198       System.err.println("");
199       System.err.print(command_parser.getHelp());
200       System.exit(1);
201     }
202
203     Document inputLasDoc = null;
204     if (in_xml != null && in_xml != "") {
205       SAXBuilder builder = new SAXBuilder();
206       builder.setExpandEntities(false);
207       builder.setEntityResolver(new MyEntityResolver());
208       try {
209         inputLasDoc = builder.build(in_xml);
210       }
211       // Failed to read input XML.  Go on without it.
212       catch (IOException ex) {
213         System.out.println(ex.getMessage());
214         inputLasDoc = null;
215       }
216       catch (JDOMException ex) {
217         System.out.println(ex.getMessage());
218         inputLasDoc = null;
219       }
220     }
221
222     int numThredds = 0;
223     int numNetcdf = 0;
224
225     for (int id = 0; id < data.length; id++) {
226
227       DatasetsGridsAxesBean dgab = null;
228       try {
229         String url = DODSNetcdfFile.canonicalURL(data[id]);
230         NetcdfDataset ncds = ucar.nc2.dataset.NetcdfDataset.openDataset(url);
231         dgab = createBeansFromNetcdfDataset(ncds, data[id]);
232         ncds.close();
233       }
234       catch (IOException e) {
235         System.out.println("IO error = " + e);
236       }
237       Vector db = (Vector) dgab.getDatasets();
238       if (db != null && db.size() > 0) {
239
240         if (oneDataset) {
241           // We're only going to use one data set.  Accumulate all the info
242           // into that one data set.
243           DatasetBean databean = (DatasetBean) dgab.getDatasets().get(0);
244
245           // Check to see if the name has been set.
246           if (oneDb.getName() == null || oneDb.getName() == "") {
247             oneDb.setName(databean.getName());
248             // If the data set name wasn't set neither was the category
249             oneCat.setName(databean.getName());
250           }
251           // Set the category filter to include this data set.
252           FilterBean filter = new FilterBean();
253           filter.setAction("apply-dataset");
254           filter.setContainstag(databean.getElement());
255           oneCat.addFilter(filter);
256
257           Vector variables = (Vector) databean.getVariables();
258           // All the URL's must be fixed to not be relative to the data set
259           // URL
260           Iterator vit = variables.iterator();
261           while (vit.hasNext()) {
262             VariableBean vb = (VariableBean) vit.next();
263             String url = databean.getUrl() + vb.getUrl();
264             vb.setUrl(url);
265           }
266           oneDb.addAllVariables(variables);
267           if (oneDb.getElement() == null || oneDb.getElement() == "") {
268             oneDb.setElement(databean.getElement());
269           }
270
271           Vector grids = dgab.getGrids();
272           Vector existingGrids = oneDgab.getGrids();
273           if (existingGrids == null) {
274             existingGrids = new Vector();
275           }
276           existingGrids.addAll(grids);
277           oneDgab.setGrids(existingGrids);
278
279           Vector axes = dgab.getAxes();
280           Vector existingAxes = oneDgab.getAxes();
281           if (existingAxes == null) {
282             existingAxes = new Vector();
283           }
284           existingAxes.addAll(axes);
285           oneDgab.setAxes(existingAxes);
286
287         }
288         else {
289
290           Document lasdoc = createXMLfromDatasetsGridsAxesBean(dgab);
291
292           String ofile = getOutfileName(basename);
293           if (inputLasDoc != null) {
294
295             String entityName = getEntityName(ofile);
296
297             // Add an entity reference to the input document if it exists.
298             EntityRef entityReference = new EntityRef(entityName, ofile);
299             addEntityRef(inputLasDoc, entityName, ofile, entityReference);
300           }
301           Element lasdata = lasdoc.getRootElement();
302           Element datasets = lasdata.getChild("datasets");
303           numNetcdf = datasets.getChildren().size();
304
305           if (numNetcdf > 0) {
306             outputXML(ofile, datasets, false);
307             Element grids = lasdata.getChild("grids");
308             outputXML(ofile, grids, true);
309             Element axes = lasdata.getChild("axes");
310             outputXML(ofile, axes, true);
311             if (category) {
312               for (Iterator cit = datasets.getChildren().iterator();
313                    cit.hasNext(); ) {
314                 Element datasetElem = (Element) cit.next();
315                 CategoryBean ds_category = new CategoryBean();
316                 ds_category.setName(datasetElem.getAttribute("name").getValue());
317                 FilterBean filter = new FilterBean();
318                 filter.setAction("apply-dataset");
319                 filter.setContainstag(datasetElem.getName());
320                 ds_category.addFilter(filter);
321                 Element lc = new Element("las_categories");
322                 lc.addContent(ds_category.toXml());
323                 outputXML(ofile, lc, true);
324               }
325             }
326           }
327         }
328       }
329     }
330
331     if (oneDataset) {
332       // We lumped all the netCDF arguements into one data set.  Now we
333       // need to print them to an entity and fix up the --xml file if
334       // it exists.
335
336       // Edit the entity string of the existing document and print it out.
337
338       // We've been stuffing in the dataset info into oneDb.  Stuff it into
339       // oneDgab and print it.
340       Vector dsets = new Vector();
341       dsets.add(oneDb);
342       oneDgab.setDatasets(dsets);
343
344       Document lasdoc = createXMLfromDatasetsGridsAxesBean(oneDgab);
345       String ofile = getOutfileName(basename);
346       if (inputLasDoc != null) {
347
348         String entityName = getEntityName(ofile);
349
350         // Add an entity reference to the input document if it exists.
351         EntityRef entityReference = new EntityRef(entityName, ofile);
352         addEntityRef(inputLasDoc, entityName, ofile, entityReference);
353       }
354       Element lasdata = lasdoc.getRootElement();
355       Element datasets = lasdata.getChild("datasets");
356       numNetcdf = datasets.getChildren().size();
357       if (numNetcdf > 0) {
358         outputXML(ofile, datasets, false);
359         Element grids = lasdata.getChild("grids");
360         outputXML(ofile, grids, true);
361         Element axes = lasdata.getChild("axes");
362         outputXML(ofile, axes, true);
363         if (category) {
364           Element lc = new Element("las_categories");
365           lc.addContent(oneCat.toXml());
366           outputXML(ofile, lc, true);
367         }
368       }
369
370     }
371
372     for (int it = 0; it < thredds.length; it++) {
373            
374       Document lasdoc = LASConfig(thredds[it], "thredds");
375       if (lasdoc != null) {
376         String ofile = getOutfileName(basename);
377         if (inputLasDoc != null) {
378           String entityName = getEntityName(ofile);
379
380           // Add an entity reference to the input document if it exists.
381           EntityRef entityReference = new EntityRef(entityName, ofile);
382           addEntityRef(inputLasDoc, entityName, ofile, entityReference);
383         }
384
385        
386         Element lasdata = lasdoc.getRootElement();
387         Element datasets = lasdata.getChild("datasets");
388
389         numThredds = datasets.getChildren().size();
390         if (numThredds > 0) {
391           outputXML(ofile, datasets, false);
392           Element grids = lasdata.getChild("grids");
393           outputXML(ofile, grids, true);
394           Element axes = lasdata.getChild("axes");
395           outputXML(ofile, axes, true);
396           Element categories = lasdata.getChild("las_categories");
397           outputXML(ofile, categories, true);
398         }
399       }
400     }
401
402     if (numThredds == 0 && numNetcdf == 0) {
403       System.err.println("");
404       System.err.println("No grids were found in the input data sets.");
405       System.err.println(
406           "Check to see if the OPeNDAP servers being referenced are running.");
407       System.err.println(
408           "Verify that the netCDF files referenced are COARDS or CF compliant.");
409       System.err.println("");
410     }
411
412     if (inputLasDoc != null) {
413       String newXmlFile;
414       if (basename.endsWith(".xml")) {
415         newXmlFile = basename.substring(0, basename.length() - 4) +
416             "_las.xml";
417       }
418       else {
419         newXmlFile = basename + "_las.xml";
420       }
421       outputXML(newXmlFile, inputLasDoc);
422     }
423   }
424
425   public static Document LASConfig(String data, String type) {
426
427     // Try to read this as a netCDF file or OPeNDAP netCDF data source.
428     if (type.equals("netcdf")) {
429
430     }
431     // Try to read this as a THREDDS Dataset Inventory Catalog
432     if (type.equals("thredds")) {
433       InvCatalogFactory factory = new InvCatalogFactory("default", false);
434       InvCatalog catalog = (InvCatalog) factory.readXML(data);
435       StringBuffer buff = new StringBuffer();
436       boolean show = false;
437       if (verbose) {
438         show = true;
439       }
440       if (!catalog.check(buff, show)) {
441         System.out.println("Invalid catalog <" + data + ">\n" + buff.toString());
442       }
443       else {
444         return createXMLfromTHREDDSCatalog(catalog);
445       }
446     }
447     // Try to read this as a THREDDS Catalog Generator Configuration file.
448     // If this works, then generate the catalog and then generate the LAS
449     // configuration.
450
451     // Skip this for now...
452     /*
453          URL url=null;
454          try {
455       url = new URL(data);
456          }
457          catch (MalformedURLException ex) {
458       System.out.println(ex.getMessage()+" "+data);
459          }
460          CatalogGen catGen = new CatalogGen(url);
461          StringBuffer log = new StringBuffer();
462          if (catGen.isValid(log)) {
463       catGen.expand();
464       catalog = catGen.getCatalog();
465       return createXMLfromTHREDDSCatalog(catalog);
466          }
467      */
468
469     return null;
470
471   }
472
473   // end of LASConfigFactory
474   /**
475    * addEntityRef
476    *
477    * @param inputLasDoc Document
478    * @param entityReference EntityRef
479    */
480   public static void addEntityRef(Document inputLasDoc,
481                                   String entityName,
482                                   String ofile,
483                                   EntityRef entityReference) {
484
485     DocType docType = inputLasDoc.getDocType();
486     String entityString = docType.getInternalSubset();
487     // Get rid of the file:/// references to the existing entities
488     String shortEntityString = "";
489     StringTokenizer tokenizer = new StringTokenizer(entityString, "\n");
490     while (tokenizer.hasMoreTokens()) {
491       String entity = tokenizer.nextToken();
492       if (entity.indexOf("file:/") >= 0) {
493         entity = entity.substring(0, entity.indexOf("file")) +
494             entity.substring(entity.lastIndexOf("/") + 1, entity.length());
495         shortEntityString += entity + "\n";
496       }
497       else {
498         shortEntityString += entity + "\n";
499       }
500     }
501     entityString = shortEntityString +
502         "  <!ENTITY " + entityName + " SYSTEM \"" + ofile + "\">\n";
503     docType.setInternalSubset(entityString);
504     Element lasdata = inputLasDoc.getRootElement();
505     lasdata.addContent(entityReference);
506     lasdata.addContent("\n");
507
508   }
509
510   public static String getEntityName(String ofile) {
511     // Get rid of c:\ on PC file names.
512     String entityName = ofile;
513     if (entityName.startsWith(":\\", 1)) {
514       entityName = entityName.substring(3, ofile.length());
515       // Get rid of the ".xml" in the name.
516     }
517     entityName = entityName.substring(0, entityName.length() - 4);
518     return entityName;
519   }
520
521   /**
522    * getOutfileName
523    *
524    * @param basename String
525    * @param b boolean
526    * @return String
527    */
528
529   public static String getOutfileName(String basename) {
530
531     String ofile;
532     String countString = "";
533
534     if (use_suffix) {
535       countString = "_000";
536       if (fileCount < 10) {
537         countString = "_00" + String.valueOf(fileCount);
538       }
539       else if (fileCount >= 10 && fileCount < 99) {
540         countString = "_0" + String.valueOf(fileCount);
541       }
542       else if (fileCount >= 100 && fileCount < 1000) {
543         countString = String.valueOf(fileCount);
544       }
545       else {
546         System.err.println("No more that 999 data sets to process.  Please.");
547         System.exit(1);
548       }
549     }
550
551     if (basename.endsWith(".xml")) {
552       ofile = basename.substring(0, basename.length() - 4) +
553           countString +
554           ".xml";
555     }
556     else {
557       ofile = basename + countString + ".xml";
558     }
559
560     fileCount++;
561     return ofile;
562   }
563
564   /**
565    * createXMLfromTHREDDSCatalog
566    *
567    * @param catalog InvCatalog
568    * @return Document
569    */
570   private static Document createXMLfromTHREDDSCatalog(InvCatalog catalog) {
571
572     CategoryBean top = new CategoryBean();
573     String topName = catalog.getName();
574     if (topName != null) {
575       top.setName(catalog.getName());
576     }
577     else {
578       top.setName(catalog.getUriString());
579     }
580     Vector DGABeans = new Vector();
581     Vector CategoryBeans = new Vector();
582
583     List ThreddsDatasets = catalog.getDatasets();
584     Iterator di = ThreddsDatasets.iterator();
585     while (di.hasNext()) {
586       InvDataset ThreddsDataset = (InvDataset) di.next();
587       if (ThreddsDataset.hasNestedDatasets()) {
588         CategoryBean cb = processCategories(ThreddsDataset);
589         CategoryBeans.add(cb);
590       }
591     }
592
593     // Discover and process all the THREDDS dataset elements that actually
594     // connect to a data source.
595
596     ThreddsDatasets = catalog.getDatasets();
597     di = ThreddsDatasets.iterator();
598
599     while (di.hasNext()) {
600       InvDataset ThreddsDataset = (InvDataset) di.next();
601       DGABeans.addAll(processDatasets(ThreddsDataset));
602     }
603
604     top.setCategories(CategoryBeans);
605     // create las_categories and datasets elements at this level
606     Document doc = new Document();
607     Element lasdata = new Element("lasdata");
608     Element datasetsElement = new Element("datasets");
609     Element gridsElement = new Element("grids");
610     Element axesElement = new Element("axes");
611
612     Iterator dgabit = DGABeans.iterator();
613     while (dgabit.hasNext()) {
614       DatasetsGridsAxesBean dgab_temp = (DatasetsGridsAxesBean) dgabit.next();
615       if (dgab_temp.getError() != null) {
616         lasdata.addContent(new Comment(dgab_temp.getError()));
617       }
618       else {
619         Vector datasets = dgab_temp.getDatasets();
620         Iterator dsit = datasets.iterator();
621         while (dsit.hasNext()) {
622           DatasetBean db = (DatasetBean) dsit.next();
623           Element dsE = db.toXml();
624           datasetsElement.addContent(dsE);
625         }
626         Vector grids = dgab_temp.getGrids();
627         Iterator git = grids.iterator();
628         while (git.hasNext()) {
629           GridBean gb = (GridBean) git.next();
630           Element gE = gb.toXml();
631           gridsElement.addContent(gE);
632         }
633         Vector axes = dgab_temp.getAxes();
634         Iterator ait = axes.iterator();
635         while (ait.hasNext()) {
636           AxisBean ab = (AxisBean) ait.next();
637           Element aE = ab.toXml();
638           axesElement.addContent(aE);
639         }
640       }
641     }
642
643     Element las_categories = new Element("las_categories");
644     Element topElement = top.toXml();
645     las_categories.addContent(topElement);
646     lasdata.addContent(datasetsElement);
647     lasdata.addContent(gridsElement);
648     lasdata.addContent(axesElement);
649     lasdata.addContent(las_categories);
650     doc.setRootElement(lasdata);
651     return doc;
652   }
653
654   /**
655    * processDataset
656    *
657    * @param ThreddsDataset InvDataset
658    * @return DatasetBean
659    */
660   private static Vector processDatasets(InvDataset ThreddsDataset) {
661      
662     Vector beans = new Vector();
663     if (ThreddsDataset.hasAccess()) {
664       boolean done = false;
665       for (Iterator iter = ThreddsDataset.getAccess().iterator();
666            iter.hasNext(); ) {
667         InvAccess access = (InvAccess) iter.next();
668         if ( (access.getService().getServiceType() == ServiceType.DODS ||
669               access.getService().getServiceType() == ServiceType.NETCDF ||
670               access.getService().getServiceType() == ServiceType.OPENDAP) &&
671             !done) {
672           done = true;
673           DatasetsGridsAxesBean dgab =
674               createBeansFromThreddsDataset(ThreddsDataset, access);
675           beans.add(dgab);
676         }
677       }
678     }
679     for (Iterator iter = ThreddsDataset.getDatasets().iterator();
680          iter.hasNext(); ) {
681       beans.addAll(processDatasets( (InvDataset) iter.next()));
682     }
683     return beans;
684   }
685
686   /**
687    * createDatasetBeanFromThreddsDataset
688    *
689    * @param ThreddsDataset InvDataset
690    * @return DatasetBean
691    */
692   private static DatasetsGridsAxesBean createBeansFromThreddsDataset(
693       InvDataset ThreddsDataset, InvAccess access) {
694     DatasetsGridsAxesBean dgab = new DatasetsGridsAxesBean();
695     ThreddsMetadata.GeospatialCoverage gc = ThreddsDataset.
696         getGeospatialCoverage();
697     ThreddsMetadata.TimeCoverage tc = ThreddsDataset.getTimeCoverage();
698     if (gc == null || tc == null) {
699       // The geospatial informaiton or time coverage information
700       // is not included in the catalog, so we'll have to pull it out
701       // of the data source ourselves.
702       String url = access.getStandardUrlName();
703       try {
704         String dods = url.replaceAll("http", "dods");
705         NetcdfDataset ncds = ucar.nc2.dataset.NetcdfDataset.openDataset(dods);
706         dgab = createBeansFromNetcdfDataset(ncds, url);
707         ncds.close();
708       }
709       catch (IOException e) {
710         dgab.setError(e.getMessage());
711         System.out.println("IO error = " + e.getMessage());
712       }
713
714     }
715     else {
716       // Create the LAS geo axes from the gc object.
717     }
718     return dgab;
719   }
720
721   /**
722    * processCategories
723    *
724    * @param ThreddsDataset InvDataset
725    * @return CategoryBean
726    */
727   private static CategoryBean processCategories(InvDataset ThreddsDataset) {
728    
729     CategoryBean cb = new CategoryBean();
730     // Make any THREDDS documentation links into LAS contributor links.
731     List docs = ThreddsDataset.getDocumentation();
732     Vector contribs = new Vector();
733     for (Iterator dit = docs.iterator(); dit.hasNext(); ) {
734       InvDocumentation doc = (InvDocumentation) dit.next();
735       if (doc.hasXlink()) {
736         ContributorBean contributor = new ContributorBean();
737         contributor.setRole("THREDDS Metadata");
738         contributor.setUrl(doc.getXlinkHref());
739         contributor.setName(doc.getXlinkTitle());
740         contribs.add(contributor);
741       }
742     }
743     cb.setContributors(contribs);
744
745     String name = ThreddsDataset.getName();
746     if (name != null) {
747       cb.setName(ThreddsDataset.getName());
748     }
749     else {
750       cb.setName("THREDDS Dataset");
751     }
752     if (ThreddsDataset.hasAccess()) {
753       // Make the filter.
754       FilterBean filter = new FilterBean();
755       filter.setAction("apply-dataset");
756       /**
757        * @todo  We're doing this work twice.  If we were smarter we
758        * wouldn't be.
759        */
760
761       // This will create a filter that doesn't match anything.
762       // The LAS interface generator will ignore this category.
763       String url = "yadayada";
764
765       for (Iterator ait = ThreddsDataset.getAccess().iterator();
766            ait.hasNext(); ) {
767         InvAccess access = (InvAccess) ait.next();
768         if (access.getService().getServiceType() == ServiceType.DODS ||
769             access.getService().getServiceType() == ServiceType.OPENDAP ||
770             access.getService().getServiceType() == ServiceType.NETCDF) {
771           url = access.getStandardUrlName();
772         }
773       }
774
775       String tag;
776       try {
777         MessageDigest md;
778         md = MessageDigest.getInstance("SHA-1");
779         byte[] result = md.digest(url.getBytes());
780         tag =
781             "id-" +
782             hexEncode(result).substring(result.length / 2, result.length);
783       }
784       catch (NoSuchAlgorithmException e) {
785         System.out.println("Cannot create SHA-1 hash." + e.getMessage());
786         tag = "id-12345";
787       }
788
789       filter.setContainstag(tag);
790       cb.addFilter(filter);
791     }
792      
793     Vector subCats = new Vector();
794     for (Iterator subDatasetsIt = ThreddsDataset.getDatasets().iterator();
795          subDatasetsIt.hasNext(); ) {
796       InvDataset subDataset = (InvDataset) subDatasetsIt.next();
797       // Process the sub-categories
798       CategoryBean subCat = processCategories(subDataset);
799       subCats.add(subCat);
800     }
801     cb.setCategories(subCats);
802
803     return cb;
804   }
805
806   /**
807    * createXMLfromNetcdfDataset
808    *
809    * @param ncds NetcdfDataset
810    * @param url String
811    * @return Document
812    */
813   public static DatasetsGridsAxesBean createBeansFromNetcdfDataset(
814       NetcdfDataset ncds,
815       String url) {
816
817     DatasetsGridsAxesBean dagb = new DatasetsGridsAxesBean();
818     Vector DatasetBeans = new Vector();
819     DatasetBean dataset = new DatasetBean();
820     UniqueVector GridBeans = new UniqueVector();
821     UniqueVector AxisBeans = new UniqueVector();
822
823     dataset.setVersion(version_string);
824     dataset.setCreator(addXML.class.getName());
825
826     if (verbose) {
827       System.out.println("Processing netCDF dataset: " + url);
828     }
829
830     // Get a list of grids
831
832     Attribute nameAttribute = null;
833     if (global_title_attribute == null) {
834       nameAttribute = ncds.findGlobalAttributeIgnoreCase("long_name");
835       if (nameAttribute == null) {
836         nameAttribute = ncds.findGlobalAttributeIgnoreCase("title");
837       }
838     }
839     else {
840       nameAttribute = ncds.findGlobalAttributeIgnoreCase(global_title_attribute);
841     }
842     String name = null;
843
844     if (nameAttribute != null) {
845       if (nameAttribute.isString()) {
846         name = nameAttribute.getStringValue();
847       }
848     }
849     GridDataset gridDs = new GridDataset(ncds);
850     if (name == null) {
851       name = url;
852     }
853
854     String elementName = name;
855
856     try {
857       MessageDigest md;
858       md = MessageDigest.getInstance("SHA-1");
859       byte[] result = md.digest(url.getBytes());
860       elementName =
861           "id-" +
862           hexEncode(result).substring(result.length / 2, result.length);
863     }
864     catch (NoSuchAlgorithmException e) {
865       System.out.println("Cannot create SHA-1 hash." + e.getMessage());
866       elementName = "id-12345";
867     }
868
869     dataset.setName(name);
870     dataset.setElement(elementName);
871     dataset.setUrl(url);
872
873     List grids = gridDs.getGrids();
874
875     if (grids.size() == 0) {
876       dataset.setComment(
877           "This data source has no lat/lon grids that follow a known convention.");
878       System.err.println("File parsed.  No Lat/Lon grids found.");
879     }
880     for (int i = 0; i < grids.size(); i++) {
881       /*
882        * A GridDataset can contain one or more GeoGrid objects each
883        * potentially defined on its own axes.
884        *
885        * A Java netCDF GeoGrid contains the same information the combination
886        * of an LAS variable, grid, and assoicated axes.
887        *
888        * To make the resulting XML fragment as compact as possible,
889        * this code attempts to get the smallest set of LAS grids
890        * necessary.
891        */
892
893       UniqueVector GridAxisBeans = new UniqueVector();
894
895       GeoGrid geogrid = (GeoGrid) grids.get(i);
896
897       VariableBean variable = new VariableBean();
898       variable.setUrl("#" + geogrid.getName());
899       if (!geogrid.getDescription().equals(geogrid.getName())) {
900         variable.setName(geogrid.getDescription());
901       }
902       else {
903         variable.setName(geogrid.getName());
904       }
905       variable.setElement(geogrid.getName() + "-" + elementName);
906       variable.setUnits(geogrid.getUnitsString());
907
908       GridCoordSys gcs = geogrid.getCoordinateSystem();
909
910       GridBean grid = new GridBean();
911
912       String grid_name = "grid";
913       CoordinateAxis1D xAxis = (CoordinateAxis1D) gcs.getXHorizAxis();
914       grid_name = grid_name + "-" + xAxis.getShortName();
915       if (verbose) {
916         System.out.println("\t Variable: " + geogrid.getName());
917         System.out.print("\t\t Longitude axis: ");
918       }
919       AxisBean xaxis = makeGeoAxis(xAxis, "x", elementName);
920       GridAxisBeans.addUnique(xaxis);
921       if (verbose) {
922         System.out.println(xaxis.toString());
923       }
924       CoordinateAxis1D yAxis = (CoordinateAxis1D) gcs.getYHorizAxis();
925       grid_name = grid_name + "-" + yAxis.getShortName();
926       if (verbose) {
927         System.out.print("\t\t Latitude axis: ");
928       }
929       AxisBean yaxis = makeGeoAxis(yAxis, "y", elementName);
930       GridAxisBeans.addUnique(yaxis);
931       if (verbose) {
932         System.out.println(yaxis.toString());
933       }
934       if (gcs.hasVerticalAxis()) {
935         CoordinateAxis1D zAxis = gcs.getVerticalAxis();
936         grid_name = grid_name + "-" + zAxis.getShortName();
937         if (verbose) {
938           System.out.print("\t\t Vertical axis: ");
939         }
940         AxisBean zaxis = makeGeoAxis(zAxis, "z", elementName);
941         GridAxisBeans.addUnique(zaxis);
942         if (verbose) {
943           System.out.println(zaxis.toString());
944         }
945
946       }
947       else {
948         if (verbose) {
949           System.out.println("\t\t No vertical axis");
950         }
951       }
952
953       CoordinateAxis1D tAxis = gcs.getTimeAxis();
954
955       if (tAxis != null) {
956         grid_name = grid_name + "-" + tAxis.getShortName();
957         if (verbose) {
958           System.out.print("\t\t Time axis: ");
959         }
960         AxisBean taxis = makeTimeAxis(tAxis, elementName);
961         GridAxisBeans.addUnique(taxis);
962         if (verbose) {
963           System.out.println(taxis.toString());
964         }
965       }
966       else {
967         System.out.println("\t\t No time axis");
968       }
969
970       grid.setElement(grid_name + "-" + elementName);
971       grid.setAxes(GridAxisBeans);
972       variable.setGrid(grid);
973       dataset.addVariable(variable);
974       ProjectionImpl proj = geogrid.getProjection();
975
976       if (verbose) {
977         if (proj instanceof LatLonProjection) {
978           System.out.println("\t\t Grid has LatLonProjection.");
979         }
980         else if (proj instanceof LambertConformal) {
981           System.out.println("\t\t Grid has Lambert Conformal projection...");
982         }
983         else if (proj instanceof Stereographic) {
984           System.out.println("\t\t Grid has Stereographic projection...");
985         }
986         else if (proj instanceof TransverseMercator) {
987           System.out.println("\t\t Grid has Transvers Mercator projection...");
988         }
989         else {
990           System.out.println("\t\t Grid has unknown projection...");
991         }
992       }
993
994       // Add the axis beans for this grid to the list of axis in this data source.
995       for (Iterator abit = GridAxisBeans.iterator(); abit.hasNext(); ) {
996         AxisBean ab = (AxisBean)abit.next();
997         AxisBeans.addUnique(ab);
998       }
999       GridBeans.addUnique(grid);
1000
1001     } // Loop over Grids
1002     DatasetBeans.add(dataset);
1003     dagb.setDatasets(DatasetBeans);
1004     dagb.setGrids(GridBeans);
1005     dagb.setAxes(AxisBeans);
1006     return dagb;
1007   }
1008
1009   public static org.jdom.Document createXMLfromNetcdfDataset(NetcdfDataset
1010       ncds,
1011       String url) {
1012     DatasetsGridsAxesBean beans = createBeansFromNetcdfDataset(ncds, url);
1013     DatasetBean dataset = (DatasetBean) beans.getDatasets().get(0);
1014     Vector GridBeans = (Vector) beans.getGrids();
1015     Vector AxisBeans = (Vector) beans.getAxes();
1016
1017     Document doc = new Document();
1018     Element lasdata = new Element("lasdata");
1019     doc.setRootElement(lasdata);
1020     Element datasetsElement = new Element("datasets");
1021     Element thisDataset = dataset.toXml();
1022     datasetsElement.addContent(thisDataset);
1023     lasdata.addContent(datasetsElement);
1024     Element gridsElement = new Element("grids");
1025     Iterator git = GridBeans.iterator();
1026     while (git.hasNext()) {
1027       GridBean gb = (GridBean) git.next();
1028       Element gridElement = gb.toXml();
1029       gridsElement.addContent(gridElement);
1030     }
1031     lasdata.addContent(gridsElement);
1032
1033     Element axesElement = new Element("axes");
1034     Iterator ait = AxisBeans.iterator();
1035     while (ait.hasNext()) {
1036       AxisBean ab = (AxisBean) ait.next();
1037       Element axisElement = ab.toXml();
1038       axesElement.addContent(axisElement);
1039     }
1040
1041     lasdata.addContent(axesElement);
1042     return doc;
1043
1044   } // end of createXMLfromNetcdfDataset
1045
1046   public static org.jdom.Document createXMLfromDatasetsGridsAxesBean(
1047       DatasetsGridsAxesBean beans) {
1048
1049     Vector dataset = (Vector) beans.getDatasets();
1050     Vector GridBeans = (Vector) beans.getGrids();
1051     Vector AxisBeans = (Vector) beans.getAxes();
1052
1053     Document doc = new Document();
1054     Element lasdata = new Element("lasdata");
1055     doc.setRootElement(lasdata);
1056     Element datasetsElement = new Element("datasets");
1057     Iterator dit = dataset.iterator();
1058     while (dit.hasNext()) {
1059       DatasetBean d = (DatasetBean) dit.next();
1060       Element thisDataset = d.toXml();
1061       datasetsElement.addContent(thisDataset);
1062     }
1063     lasdata.addContent(datasetsElement);
1064     Element gridsElement = new Element("grids");
1065     Iterator git = GridBeans.iterator();
1066     while (git.hasNext()) {
1067       GridBean gb = (GridBean) git.next();
1068       Element gridElement = gb.toXml();
1069       gridsElement.addContent(gridElement);
1070     }
1071     lasdata.addContent(gridsElement);
1072
1073     Element axesElement = new Element("axes");
1074     Iterator ait = AxisBeans.iterator();
1075     while (ait.hasNext()) {
1076       AxisBean ab = (AxisBean) ait.next();
1077       Element axisElement = ab.toXml();
1078       axesElement.addContent(axisElement);
1079     }
1080
1081     lasdata.addContent(axesElement);
1082     return doc;
1083
1084   } // end of createXMLfromDatasetsGridsAxesBean
1085
1086   static private AxisBean makeTimeAxis(CoordinateAxis1D axis, String id) {
1087
1088     // LAS only understands time units of: 'year', 'month', 'day', and 'hour'
1089
1090     String patterns[] = {
1091         "yyyy", "yyyy-MM-dd", "yyyy-MM-dd", "yyyy-MM-dd",
1092         "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss",
1093         "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss"};
1094     String type = "t";
1095     AxisBean axisbean = new AxisBean();
1096     axisbean.setType(type);
1097     axisbean.setElement(axis.getShortName() + "-" + type + "-" + id);
1098     ArangeBean arange = new ArangeBean();
1099
1100     // Do a bunch of extra work to make this time axis into what LAS needs.
1101
1102     boolean zeroOrigin = false;
1103     boolean useV = false;
1104
1105     // Get a Java UDUNITS unit representation of this date string.
1106     String unitsString = axis.getUnitsString().toLowerCase();
1107
1108     unitsString = unitsString.trim();
1109
1110     // If the units contains year 0000, we're going to have to
1111     // boost the dates over into year 1 so we don't have to deal
1112     // with negative years.  (There is no year 0 in the chronology
1113     // we're using.
1114
1115     if (unitsString.indexOf("0000") >= 0) {
1116       unitsString.replaceFirst("0000", "0001");
1117       zeroOrigin = true;
1118     }
1119
1120     // If it doesn't contain the word "since" and some ":" we probably can't
1121     // decode the times, so we're just going to dump them into the file.
1122     if (unitsString.indexOf("since") < 0 && unitsString.indexOf(":") < 0  ) {
1123       useV = true;
1124     }
1125     // This if test checks to see if this units string ends in an alpha character
1126     // If it does, we're thinking there is some time zone stuff at the end that
1127     // should be chopped off.  It's probably a bug in SimpleUnit that should
1128     // be reported.
1129     String last = unitsString.toLowerCase().substring(unitsString.length()-1,unitsString.length());
1130     if (unitsString.substring(unitsString.length()-1,unitsString.length()).matches("[a-zA-Z]")) {
1131           unitsString = unitsString.substring(0, unitsString.lastIndexOf(" "));
1132     }
1133
1134     double t[] = axis.getCoordValues();
1135
1136     // Units sting is one of the bogus "day", "month", etc and there
1137     // is no time_origin attribute so just dump it out as <v> elements.
1138     if (useV) {
1139       // In this case do not attempt to decode the time elements
1140       axisbean.setUnits(unitsString);
1141       axisbean.setArange(null);
1142       String ts[] = new String[t.length];
1143       for (int i = 0; i < t.length; i++) {
1144         ts[i] = String.valueOf(t[i]);
1145       }
1146       axisbean.setV(ts);
1147       return axisbean;
1148     }
1149
1150     DateUnit dateUnit = (DateUnit) SimpleUnit.factory(unitsString);
1151
1152     if (dateUnit == null) {
1153       System.out.println("Not a date Unit String: " + unitsString);
1154     }
1155
1156     // This is the Joda Time Chronology that cooresponds to the
1157     // Java Gregorian Calendar and the Udunits Calendar.
1158
1159     // Always use this chronology and the UTC Time Zone.
1160     Chronology chrono = Chronology.getGJ();
1161
1162     if (t.length > 2) {
1163
1164       Date date1 = dateUnit.getStandardDate(t[0]);
1165       Date date2 = dateUnit.getStandardDate(t[1]);
1166       DateTime jodaDate1 = new DateTime(date1, chrono);
1167       DateTime jodaDate2 = new DateTime(date2, chrono);
1168       Period period =
1169           new Period(jodaDate1.withZone(DateTimeZone.UTC),
1170                      jodaDate2.withZone(DateTimeZone.UTC));
1171
1172       // Returns the number of years, months, weeks, days,
1173       // hours, minutes, seconds, and millis between these
1174       // two dates.
1175
1176       // Only one should be greater than 0.
1177       int numPeriods = 0;
1178       String periods = "";
1179       int values[] = period.getValues();
1180       DurationFieldType types[] = period.getFieldTypes();
1181       DateTimeFormatter fmt = null;
1182       for (int i = 0; i < values.length; i++) {
1183         if (values[i] > 0) {
1184           numPeriods++;
1185           fmt = DateTimeFormat.forPattern(patterns[i]);
1186           int step = values[i];
1187           String typeName = types[i].getName();
1188           // Get rid of the "s" in the plural form of the name.
1189           typeName = typeName.substring(0, typeName.length() - 1);
1190           // LAS doesn't understand "week" so make it "day" and
1191           // multiply the step by 7.
1192           periods = periods + " " + typeName;
1193           if (typeName.equals("week")) {
1194             typeName = "day";
1195             step = step * 7;
1196           }
1197           axisbean.setUnits(typeName);
1198           arange.setStep(String.valueOf(step));
1199         }
1200       }
1201       if (numPeriods > 1) {
1202
1203         // This is special code to deal with climatology files that define
1204         // the time axis in the "middle" of the month.  Since there is no
1205         // "middle" of months (but there is certainly always a
1206         // first of the month, geez-o) so we are left to figure this out by
1207         // looking at the period values and guessing that this really means
1208         // climo months.
1209
1210         // Is the gap in years and months 0?
1211         // Are the values 4 weeks apart?
1212         if ( (values[0] == 0 && values[1] == 0) &&
1213             (values[2] == 4)) {
1214           // We're guessing these are months
1215           axisbean.setUnits("month");
1216           arange.setStep("1");
1217         }
1218         else {
1219           System.out.println("Too many periods: " + periods);
1220           //Try just dumping out the formatted times
1221           axisbean.setArange(null);
1222           String ts[] = new String[t.length];
1223           for (int i = 0; i < t.length; i++) {
1224             DateTime dt =
1225                 new DateTime(dateUnit.getStandardDate(t[i]), chrono);
1226             ts[i] = fmt.print(dt.withZone(DateTimeZone.UTC));
1227           }
1228           axisbean.setV(ts);
1229
1230         }
1231       }
1232       Boolean forceAxis = (Boolean) forceAxes.get("t");
1233       if ( (axis.isRegular() || axisbean.getUnits().equals("month")) ||
1234           forceAxis.booleanValue()) {
1235
1236         // Months are "regular" according to LAS, but not according
1237         // to the isRegular() test, so we need special code for
1238         // months.
1239
1240         /** @todo Check to make sure that if it's months it really is regular. */
1241
1242         arange.setSize(String.valueOf(axis.getSize()));
1243         arange.setStart(fmt.print(jodaDate1.withZone(DateTimeZone.UTC)));
1244         axisbean.setArange(arange);
1245       }
1246       else {
1247         // Layout the time axis using "v" elements.
1248
1249         axisbean.setArange(null);
1250         String ts[] = new String[t.length];
1251         for (int i = 0; i < t.length; i++) {
1252           DateTime dt =
1253               new DateTime(dateUnit.getStandardDate(t[i]), chrono);
1254           ts[i] = fmt.print(dt.withZone(DateTimeZone.UTC));
1255         }
1256         axisbean.setV(ts);
1257
1258       }
1259     }
1260     else {
1261       // Time axis has only one time step.
1262       // We have no idea what this single time point looks like so,
1263       // format it for all year, month, day, hours, minutes, seconds
1264       DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
1265       Date date1 = dateUnit.getStandardDate(t[0]);
1266       DateTime jodaDate1 = new DateTime(date1, DateTimeZone.UTC);
1267       arange.setSize("1");
1268       arange.setStep("1");
1269       arange.setStart(fmt.print(jodaDate1));
1270       // The UI will eliminate this axis.  I think it's safe to set
1271       // the units to anything.  !?!?
1272       axisbean.setUnits("day");
1273       axisbean.setArange(arange);
1274     }
1275
1276     return axisbean;
1277   }
1278
1279   static private AxisBean
1280       makeGeoAxis(CoordinateAxis1D axis, String type, String id) {
1281     DecimalFormat fmt = new DecimalFormat("####.####");
1282     AxisBean axisbean = new AxisBean();
1283     axisbean.setType(type);
1284     axisbean.setElement(axis.getShortName() + "-" + type + "-" + id);
1285     ArangeBean arange = new ArangeBean();
1286     axisbean.setUnits(axis.getUnitsString());
1287     Boolean forceAxis = (Boolean) forceAxes.get(type);
1288     if ( (axis.isRegular() && axis.getSize() > 1) || forceAxis.booleanValue()) {
1289       arange.setSize(String.valueOf(axis.getSize()));
1290       double delta = axis.getIncrement();
1291       double start = axis.getStart();
1292       // Flip so that the axis is South to North
1293       if (delta < 0 && type.equals("y")) {
1294         start = start + (axis.getSize() - 1) * delta;
1295         delta = -delta;
1296       }
1297       arange.setStep(fmt.format(delta));
1298       arange.setStart(fmt.format(start));
1299       axisbean.setArange(arange);
1300     }
1301     else {
1302       double[] v = axis.getCoordValues();
1303
1304       // List Latitude south to north
1305       if (v[0] > v[v.length - 1] && type.equals("y")) {
1306         reverse(v);
1307       }
1308       axisbean.setArange(null);
1309
1310       /** @todo we want to use 4-log[base10](max-min). */
1311       String[] vs = new String[v.length];
1312       for (int i = 0; i < v.length; i++) {
1313         vs[i] = fmt.format(v[i]);
1314       }
1315       axisbean.setV(vs);
1316
1317     }
1318     return axisbean;
1319
1320   }
1321
1322   /**
1323    * The byte[] returned by MessageDigest does not have a nice
1324    * textual representation, so some form of encoding is usually performed.
1325    *
1326    * This implementation follows the example of David Flanagan's book
1327    * "Java In A Nutshell", and converts a byte array into a String
1328    * of hex characters.
1329    *
1330    * Another popular alternative is to use a "Base64" encoding.
1331    */
1332   static private String hexEncode(byte[] aInput) {
1333     StringBuffer result = new StringBuffer();
1334     char[] digits = {
1335         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
1336         'e', 'f'};
1337     for (int idx = 0; idx < aInput.length; ++idx) {
1338       byte b = aInput[idx];
1339       result.append(digits[ (b & 0xf0) >> 4]);
1340       result.append(digits[b & 0x0f]);
1341     }
1342     return result.toString();
1343   }
1344
1345 //========================================================= reverse
1346 // Stolen directly off the web from:
1347 // http://leepoint.net/notes-java/data/arrays/34arrayreverse.html
1348 // Converted from int array to double...
1349   public static void reverse(double[] b) {
1350     int left = 0; // index of leftmost element
1351     int right = b.length - 1; // index of rightmost element
1352
1353     while (left < right) {
1354       // exchange the left and right elements
1355       double temp = b[left];
1356       b[left] = b[right];
1357       b[right] = temp;
1358
1359       // move the bounds toward the center
1360       left++;
1361       right--;
1362     }
1363   } //endmethod reverse
1364
1365   static public void outputXML(String outfile, Document doc) {
1366     try {
1367       File outputFile = new File(outfile);
1368       FileWriter xmlout = new FileWriter(outputFile);
1369       org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
1370       format.setLineSeparator(System.getProperty("line.separator"));
1371       XMLOutputter outputter =
1372           new XMLOutputter(format);
1373       outputter.output(doc, xmlout);
1374       // Close the FileWriter
1375       xmlout.close();
1376     }
1377     catch (java.io.IOException e) {
1378       System.out.println(e.getMessage());
1379       e.printStackTrace();
1380     }
1381   }
1382
1383   static public void outputXML(String outfile, Element element, boolean append) {
1384     try {
1385       File outputFile = new File(outfile);
1386       FileWriter xmlout = new FileWriter(outputFile, append);
1387       org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
1388       format.setLineSeparator(System.getProperty("line.separator"));
1389       XMLOutputter outputter =
1390           new XMLOutputter(format);
1391       outputter.output(element, xmlout);
1392       xmlout.write("\n");
1393       // Close the FileWriter
1394       xmlout.close();
1395     }
1396     catch (java.io.IOException e) {
1397       System.out.println(e.getMessage());
1398       e.printStackTrace();
1399     }
1400   }
1401
1402   private void jbInit() throws Exception {
1403   }
1404
1405 } // end of class
1406
Note: See TracBrowser for help on using the browser.