source: 1dwg/trunk/IgorPro/cansasXML.ipf @ 12

Last change on this file since 12 was 12, checked in by prjemian, 14 years ago

added workaround to IgorPro? code for XML namespace differences on Mac and PC; propose a change to the standard XML header

File size: 32.3 KB
Line 
1#pragma rtGlobals=1             // Use modern global access method.
2#pragma version=1.02
3
4// file:        cansasXML.ipf
5// author:      Pete R. Jemian <jemian@anl.gov>
6// date:        2008-03-16
7// purpose:  implement an IgorPro file reader to read the canSAS 1-D reduced SAS data in XML files
8//                      adheres to the cansas1d/1.0 standard
9// URL: http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
10
11FUNCTION CS_XmlReader(fileName)
12        //
13        // open a canSAS 1-D reduced SAS XML data file
14        //      returns:
15        //              0 : successful
16        //              -1: XML file not found
17        //              -2: root element is not <SASroot>
18        //              -3: <SASroot> version  is not 1.0
19        //              -4: no <SASentry> elements
20        //
21        STRING fileName
22        STRING origFolder
23        STRING workingFolder = "root:Packages:CS_XMLreader"
24        VARIABLE returnCode
25
26        //
27        // set up a work folder within root:Packages
28        // Clear out any progress/results from previous activities
29        //
30        origFolder = GetDataFolder(1)
31        SetDataFolder root:                                     // start in the root data folder
32        NewDataFolder/O  root:Packages          // good practice
33        KillDataFolder/Z  $workingFolder                // clear out any previous work
34        NewDataFolder/O/S  $workingFolder       // Do all our work in root:XMLreader
35
36        //
37        // Try to open the named XML file (clean-up and return if failure)
38        //
39        VARIABLE fileID
40        STRING/G errorMsg, xmlFile
41        MAKE/T/N=(0,3)/O metadata
42        CS_appendMetaData("xmlFile", "", fileName)
43        xmlFile = fileName
44        fileID = XmlOpenFile(fileName)                  // open and parse the XMLfile
45        IF ( fileID < 0 )
46                SWITCH(fileID)                                  // fileID holds the return code; check it
47                        CASE -1:
48                                errorMsg = fileName + ": failed to parse XML"
49                        BREAK
50                        CASE -2:
51                                errorMsg = fileName + " either not found or cannot be opened for reading"
52                        BREAK
53                ENDSWITCH
54                PRINT errorMsg
55                SetDataFolder $origFolder
56                RETURN(-1)                                              // could not find file
57        ENDIF
58
59        //
60        // index the XML element list
61        //
62        XmlElemList(fileID)                                     // load the elementlist into W_ElementList
63        WAVE/T  W_ElementList                           // This declaration comes _after_ XmlElemList() is called
64
65        // qualify the XML file, don't allow just any ole XML.
66        IF ( CmpStr(W_ElementList[0][3], "SASroot") != 0 )              // deftly avoid namespace
67                errorMsg = fileName + ": root element is not <SASroot>"
68                PRINT errorMsg
69                XmlCloseFile(fileID,0)
70                SetDataFolder $origFolder
71                RETURN(-2)                                              // not a canSAS XML file
72        ENDIF
73
74        // make an index to speed up searching through column 0
75        Make/O/T/N=(DimSize(W_ElementList, 0)) W_ElementList_Col0
76        W_ElementList_Col0 = W_ElementList[p][0]
77        STRING/G nsPre = "", nsStr = ""
78        CS_registerNameSpaces(fileID)                           // to assist XPath construction
79
80        // PRJ_quick(fileID)
81
82        // identify supported versions of canSAS XML standard
83        STRING version
84        version = StringByKey("version", W_ElementList[0][2])
85        CS_appendMetaData("cansas_version", CS_XPath_NS("/SASroot/@version"), version)
86        STRSWITCH(version)     
87        CASE "1.0":                                                     // version 1.0 of the canSAS 1-D reduced SAS data standard
88                PRINT fileName, "\t\t identified as: cansas1d/1.0 XML file"
89                returnCode = CS_1i_parseXml(fileID)
90                IF (returnCode != 0)
91                        IF (strlen(errorMsg) == 0)
92                                errorMsg = "error while parsing the XML"
93                        ENDIF
94                        PRINT errorMsg
95                        XmlCloseFile(fileID,0)
96                        SetDataFolder $origFolder
97                        RETURN(returnCode)                      // error while parsing the XML
98                ENDIF
99                BREAK
100        CASE "2.0a":                                            // unsupported
101        DEFAULT:                                                        // optional default expression executed
102                errorMsg = fileName + ": <SASroot> version (" + version + ") is not supported"
103                PRINT errorMsg
104                XmlCloseFile(fileID,0)
105                SetDataFolder $origFolder
106                RETURN(-3)                                              // attribute list must include version="1.0"
107        ENDSWITCH
108
109        XmlCloseFile(fileID,0)                                  // now close the file, without saving
110        fileID = -1
111
112        SetDataFolder $origFolder
113        RETURN(0)                                                       // execution finished OK
114END
115
116// ==================================================================
117
118FUNCTION CS_1i_parseXml(fileID)
119        VARIABLE fileID
120        WAVE/T W_ElementList
121        SVAR nsStr, errorMsg
122        STRING/G Title, FolderList = ""
123        STRING XPathStr, Title_folder, SASdata_folder
124        STRING SASentryPath, SASdataPath
125        VARIABLE i, j, index, SASdata_index, returnCode = 0
126
127        // locate all the SASentry elements
128        CS_simpleXmlListXpath(fileID, "", "/SASroot//SASentry")
129        WAVE/T  W_listXPath
130        // (safer to copy W_listxpath to a local variable and allow for other calls to XMLlistXpath)
131        DUPLICATE/O/T   W_listXPath, SASentryList
132
133        IF (numpnts(SASentryList) < 1)
134                errorMsg = "could not find any SASentry elements in XML file"
135                PRINT errorMsg
136                RETURN(-4)                                              // no <SASentry> elements
137        ENDIF
138
139        // Should we test here for all required elements?  That could be tedious.  And possibly unnecessary.
140
141        // process each SASentry element
142        DUPLICATE/O/T metadata, metadata_file
143        FOR (i = 0; i < numpnts(SASentryList); i += 1)
144                index = CS_findElementIndex(SASentryList[i])
145                DUPLICATE/O/T metadata_file, metadata
146                Title = CS_1i_locateTitle(fileID, CS_correctedXpathStr(SASentryList[i]))                // look in several places or use default title
147                Title_folder = CleanupName(Title, 0)
148                IF ( CheckName(Title_folder, 11) != 0 )
149                        Title_folder = UniqueName(Title_folder, 11, 0)
150                ENDIF
151                NewDataFolder/O  $Title_folder
152                FolderList = AddListItem(Title_folder, FolderList, ";", Inf)
153                //
154                // CS_correctedXpathStr
155                SASentryPath = CS_correctedXpathStr(SASentryList[i])
156                CS_1i_collectMetadata(fileID, SASentryPath)
157                //
158                // next, extract each SASdata block into a subfolder
159                CS_simpleXmlListXpath(fileID, SASentryPath, "//SASdata")        //output: W_listXPath
160                DUPLICATE/O/T   W_listXPath, SASdataList
161                IF (numpnts(SASdataList) == 1)
162                        // only one SASdata element, place data waves in Title folder
163                        SASdataPath = CS_correctedXpathStr(SASdataList[0])
164                        SASdata_folder = ":" + Title_folder + ":"
165                        PRINT "\t\t dataFolder:", SASdata_folder
166                        IF (CS_1i_extractSasData(fileID, SASdataPath, SASdata_folder))
167                                // non-zero return code means an error, message is in errorMsg
168                                // What to do now?
169                                //      Proceed with that knowledge or discard the data folder?
170                                //      Go with the discard for now.
171                                returnCode += 1                         // for later
172                                PRINT "\t\t" + errorMsg
173                                KillDataFolder/Z $Title_folder          // only 1 SASdata
174                                // RETURN(1)                            // Can't return now.  What about other SASentry elements?
175                                BREAK
176                        ENDIF
177                        CS_appendMetaData(  "Run", "", CS_XmlStrFmXpath(fileID, SASdataPath+"/",  "../Run["+num2str(j+1)+"]"))
178                        CS_appendMetaData(  "Run_name", "", CS_XmlStrFmXpath(fileID, SASdataPath+"/",  "../Run["+num2str(j+1)+"]/@name"))
179                        CS_appendMetaData(  "SASdata_name", "", CS_XmlStrFmXpath(fileID, SASdataPath,  "/@name"))
180                ELSE
181                        // multiple SASdata elements, place data waves in subfolders
182                        DUPLICATE/O/T metadata, metadata_entry
183                        FOR (j = 0; j < numpnts(SASdataList); j += 1)
184                                DUPLICATE/O/T metadata_entry, metadata
185                                SASdata_index = CS_findElementIndex(SASdataList[j])
186                                SASdataPath = CS_correctedXpathStr(SASdataList[j])
187                                SASdata_folder = CleanupName(StringByKey("name", W_ElementList[SASdata_index][2]), 0)
188                                PRINT "\t\t dataFolder:", SASdata_folder
189                                CS_appendMetaData(  "Run"+num2str(j), "", CS_XmlStrFmXpath(fileID, SASdataPath+"/",  "../Run["+num2str(j+1)+"]"))
190                                CS_appendMetaData(  "Run"+num2str(j)+"_name", "", CS_XmlStrFmXpath(fileID, SASdataPath+"/",  "../Run["+num2str(j+1)+"]/@name"))
191                                CS_appendMetaData(  "SASdata"+num2str(j)+"_name", "", CS_XmlStrFmXpath(fileID, SASdataPath,  "/@name"))
192                                SetDataFolder $Title_folder
193                                IF ( CheckName(SASdata_folder, 11) != 0 )
194                                        SASdata_folder = UniqueName(SASdata_folder, 11, 0)
195                                ENDIF
196                                NewDataFolder/O  $SASdata_folder
197                                SetDataFolder ::
198                                SASdata_folder =  ":" + Title_folder + ":" + SASdata_folder + ":"
199                                //---
200                                IF (CS_1i_extractSasData(fileID, SASdataPath, SASdata_folder))
201                                        // non-zero return code means an error, message is in errorMsg
202                                        // What to do now?
203                                        //      Proceed with that knowledge or discard the data folder?
204                                        //      Go with the discard for now.
205                                        returnCode += 1                                         // for later
206                                        PRINT "\t\t" + errorMsg
207                                        KillDataFolder/Z $SASdata_folder                // just this SASdata
208                                        // RETURN(1)                                            // Can't return now.  What about other SASentry elements?
209                                        BREAK
210                                ENDIF
211                        ENDFOR  // many SASdata
212                ENDIF                   // 1 or many SASdata
213        ENDFOR          // each SASentry
214
215        RETURN(returnCode)
216END
217
218// ==================================================================
219
220FUNCTION CS_1i_collectMetadata(fileID, sasEntryPath)
221        VARIABLE fileID
222        STRING sasEntryPath
223        SVAR nsStr
224        VARIABLE i
225        WAVE/T metadata
226        STRING suffix = ""
227        STRING value
228        // collect some metadata
229        // first, fill a table with keywords, and XPath locations, 3rd column will be values
230
231        // handle most <SASsample> fields
232        CS_appendMetaData("sample_ID",                                          sasEntryPath+CS_XPath_NS("/SASsample/ID"), "")
233        CS_appendMetaData("sample_thickness",                           sasEntryPath+CS_XPath_NS("/SASsample/thickness"), "")
234        CS_appendMetaData("sample_thickness_unit",                      sasEntryPath+CS_XPath_NS("/SASsample/thickness/@unit"), "")
235        CS_appendMetaData("sample_transmission",                        sasEntryPath+CS_XPath_NS("/SASsample/transmission"), "")
236        CS_appendMetaData("sample_temperature",                         sasEntryPath+CS_XPath_NS("/SASsample/temperature"), "")
237        CS_appendMetaData("sample_temperature_unit",            sasEntryPath+CS_XPath_NS("/SASsample/temperature/@unit"), "")
238        CS_appendMetaData("sample_position_x",                          sasEntryPath+CS_XPath_NS("/SASsample/position/x"), "")
239        CS_appendMetaData("sample_position_x_unit",                     sasEntryPath+CS_XPath_NS("/SASsample/position/x/@unit"), "")
240        CS_appendMetaData("sample_position_y",                          sasEntryPath+CS_XPath_NS("/SASsample/position/y"), "")
241        CS_appendMetaData("sample_position_y_unit",                     sasEntryPath+CS_XPath_NS("/SASsample/position/y/@unit"), "")
242        CS_appendMetaData("sample_position_z",                          sasEntryPath+CS_XPath_NS("/SASsample/position/z"), "")
243        CS_appendMetaData("sample_position_z_unit",                     sasEntryPath+CS_XPath_NS("/SASsample/position/z/@unit"), "")
244        CS_appendMetaData("sample_orientation_roll",                    sasEntryPath+CS_XPath_NS("/SASsample/orientation/roll"), "")
245        CS_appendMetaData("sample_orientation_roll_unit",               sasEntryPath+CS_XPath_NS("/SASsample/orientation/roll/@unit"), "")
246        CS_appendMetaData("sample_orientation_pitch",           sasEntryPath+CS_XPath_NS("/SASsample/orientation/pitch"), "")
247        CS_appendMetaData("sample_orientation_pitch_unit",      sasEntryPath+CS_XPath_NS("/SASsample/orientation/pitch/@unit"), "")
248        CS_appendMetaData("sample_orientation_yaw",                     sasEntryPath+CS_XPath_NS("/SASsample/orientation/yaw"), "")
249        CS_appendMetaData("sample_orientation_yaw_unit",        sasEntryPath+CS_XPath_NS("/SASsample/orientation/yaw/@unit"), "")
250        // <SASsample><details> might appear multiple times, too!
251        CS_simpleXmlListXpath(fileID, sasEntryPath, "/SASsample//details")      //output: W_listXPath
252        DUPLICATE/O/T   W_listXPath, detailsList
253        suffix = ""
254        FOR (i = 0; i < numpnts(detailsList); i += 1)
255                IF (numpnts(detailsList) > 1)
256                        suffix = num2str(i)
257                ENDIF
258                CS_appendMetaData("sample_details"+suffix+"_name",      detailsList[i]+CS_XPath_NS("/@name"), "")
259                CS_appendMetaData("sample_details"+suffix,                      detailsList[i], "")
260        ENDFOR
261
262
263        // <SASinstrument>
264        CS_appendMetaData("Instrument_name", sasEntryPath+CS_XPath_NS("/SASinstrument/name"), "")
265
266        // <SASinstrument><SASsource>
267        CS_appendMetaData("source_name", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/@name"), "")
268        CS_appendMetaData("radiation", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/radiation"), "")
269        CS_appendMetaData("beam_shape", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/beam_shape"), "")
270        // ignore <beam_size> for now
271        CS_appendMetaData("wavelength", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength"), "")
272        CS_appendMetaData("wavelength_unit", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength/@unit"), "")
273        CS_appendMetaData("wavelength_min", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength_min"), "")
274        CS_appendMetaData("wavelength_min_unit", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength_min/@unit"), "")
275        CS_appendMetaData("wavelength_max", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength_max"), "")
276        CS_appendMetaData("wavelength_max_unit", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength_max/@unit"), "")
277        CS_appendMetaData("wavelength_spread", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength_spread"), "")
278        CS_appendMetaData("wavelength_spread_unit", sasEntryPath+CS_XPath_NS("/SASinstrument/SASsource/wavelength_spread/@unit"), "")
279
280        // ignore <SASinstrument><SAScollimation> for now
281
282        // <SASinstrument><SASdetector> might appear multiple times
283        CS_simpleXmlListXpath(fileID, sasEntryPath, "/SASinstrument//SASdetector")      //output: W_listXPath
284        DUPLICATE/O/T   W_listXPath, SASdetectorList
285        suffix = ""
286        FOR (i = 0; i < numpnts(SASdetectorList); i += 1)
287                IF (numpnts(SASdetectorList) > 1)
288                        suffix = num2str(i)
289                ENDIF
290                CS_appendMetaData("detector_name"+suffix,       SASdetectorList[i]+CS_XPath_NS("/name"), "")
291                CS_appendMetaData("SDD"+suffix,                         SASdetectorList[i]+CS_XPath_NS("/SDD"), "")
292                CS_appendMetaData("SDD"+suffix+"_unit",                 SASdetectorList[i]+CS_XPath_NS("/SDD/@unit"), "")
293                // ignore <offset> for now
294                // ignore <orientation> for now
295                // ignore <beam_center> for now
296                // ignore <pixel_size> for now
297                CS_appendMetaData("slit_length"+suffix,                 SASdetectorList[i]+CS_XPath_NS("/slit_length"), "")
298                CS_appendMetaData("slitLength"+suffix+"_unit",  SASdetectorList[i]+CS_XPath_NS("/slit_length/@unit"), "")
299        ENDFOR
300
301        // ignore <SASprocess> for now
302
303        // <SASnote> might appear multiple times
304        CS_simpleXmlListXpath(fileID, sasEntryPath, "//SASnote")        //output: W_listXPath
305        DUPLICATE/O/T   W_listXPath, SASnoteList
306        suffix = ""
307        FOR (i = 0; i < numpnts(SASnoteList); i += 1)
308                IF (numpnts(SASnoteList) > 1)
309                        suffix = num2str(i)
310                ENDIF
311                CS_appendMetaData("SASnote"+suffix+"_name",     SASnoteList[i]+CS_XPath_NS("/@name"), "")
312                CS_appendMetaData("SASnote"+suffix,                             SASnoteList[i], "")
313        ENDFOR
314
315        // +++++++++++++++++++++++++                    // try to fill the value column from the XML data
316        FOR (i = 0; i < DimSize(metadata, 0); i += 1)
317                IF (strlen(metadata[i][1]) > 0)                         // XPathStr for this entry?
318                        IF (strlen(metadata[i][2])  == 0)                       // not defined yet?
319                                value = CS_XmlStrFmXpath(fileID, metadata[i][1], "")            // get it
320                                // What if the value string has a ";" embedded?
321                                //  This will complicate (?compromise?) the wavenote "key=value;" syntax.
322                                metadata[i][2] = ReplaceString(";", value, " :semicolon: ")
323                        ENDIF
324                ENDIF
325        ENDFOR
326END
327
328// ==================================================================
329
330FUNCTION/S CS_1i_locateTitle(fileID, SASentryPath)
331        VARIABLE fileID
332        STRING SASentryPath
333        WAVE/T metadata
334        STRING TitlePath, Title
335        // /SASroot/SASentry/Title is the expected location, but it could be empty
336        TitlePath = SASentryPath+CS_XPath_NS("/Title")
337        Title = CS_XmlStrFmXpath(fileID,  TitlePath, "")
338        // search harder for a title
339        IF (strlen(Title) == 0)
340                TitlePath = SASentryPath+CS_XPath_NS("/@name")
341                Title = CS_XmlStrFmXpath(fileID,  TitlePath, "")
342        ENDIF
343        IF (strlen(Title) == 0)
344                TitlePath = SASentryPath+CS_XPath_NS("/SASsample/ID")
345                Title = CS_XmlStrFmXpath(fileID,  TitlePath, "")
346        ENDIF
347        IF (strlen(Title) == 0)
348                TitlePath = SASentryPath+CS_XPath_NS("/SASsample/@name")
349                Title = CS_XmlStrFmXpath(fileID,  TitlePath, "")
350        ENDIF
351        IF (strlen(Title) == 0)
352                // last resort: make up a title
353                Title = "SASentry"
354                TitlePath = ""
355        ENDIF
356        PRINT "\t Title:", Title
357        CS_appendMetaData("title", TitlePath, Title)
358        RETURN(Title)
359END
360
361// ==================================================================
362
363FUNCTION CS_fileExists(fileName)
364        // checks if a file can be found and opened
365        // !!! not needed by 2008-03-13 change in XmlOpenFile()
366        STRING fileName
367        VARIABLE refNum
368        Open/R/Z/P=home refNum as fileName
369        IF (V_flag == 0)
370                CLOSE refNum
371        ENDIF
372        RETURN( !V_flag )
373END
374
375// ==================================================================
376
377FUNCTION CS_appendMetaData(key, xpath, value)
378        STRING key, xpath, value
379        WAVE/T metadata
380        VARIABLE last
381        last = DimSize(metadata, 0)
382        Redimension/N=(last+1, 3) metadata
383        metadata[last][0] = key
384        metadata[last][1] = xpath
385        metadata[last][2] = value
386END
387
388// ==================================================================
389
390FUNCTION CS_findElementIndex(matchStr)
391        STRING matchStr
392        //
393        // support the canSAS XML file reader
394        // return index where   g[index][0] == matchStr
395        // return -1 if not found
396        //
397        WAVE/T W_ElementList_Col0
398
399        FindValue/TEXT=matchStr    W_ElementList_Col0
400        RETURN(V_value)
401END
402
403// ==================================================================
404
405FUNCTION CS_registerNameSpaces(fileID)
406        VARIABLE fileID
407        //
408        // Only one namespace might be used within the document and that is
409        // the one defined in the zzz:schemaLocation attribute to the <SASroot> element.
410        // Since the XMLutils XOP only extracts the schemaLocation as an attribute,
411        // we just ask for "/*/@schemaLocation" and pick the first URI as the namespace.
412        // We can use our own namespace prefix without further concern.
413        //
414        WAVE/T W_ElementList
415        SVAR nsPre
416        SVAR nsStr
417        VARIABLE i, j, index
418        STRING testStr
419        //
420        nsStr = StringFromList(0, StringByKey("schemaLocation", W_ElementList[0][2]), " ")
421        nsStr = W_ElementList[0][1]             // this is the one to use
422        IF (strlen(nsStr))
423                nsPre = "cs:"
424                nsStr = "cs=" + nsStr
425        ELSE
426                nsPre = ""
427        ENDIF
428
429        // 2008-03-14,PRJ: Now, add a workaround for the libxml2 support that affects MacOS.
430        //   When using namespaces
431        //              W_ElementList[][1] != "" (it shows a namespace for this node)
432        //   and with default libxml2 (v2.2) on MacOS,
433        //              W_ElementList[][0] != /SASroot/SASentry ...
434        //  BUT, on PCs and on MacOS with user-proveded libxml2 (?version?)
435        //              W_ElementList[][0] != /*/*[1]   or /*/* ...
436        //
437        // Need to handle either situation.
438        //  Add another column to W_ElementList that shows the corrected absolute
439        //      XPath query,  complete with namespace prefix as needed.
440        Redimension/N=(DimSize(W_ElementList,0),5) W_ElementList
441        W_ElementList[0,Inf][4] = ""                            // clear out the last column (useful only to developer)
442        W_ElementList[0][4] = CS_XPath_NS("/"+W_ElementList[0][3])
443        FOR (i = 1; i < DimSize(W_ElementList,0); i += 1)
444                IF (strlen(W_ElementList[i][4]) == 0)           // if not already set, then find absolute XPath string
445                        index = CS_findElementIndex(  CS_findLast(W_ElementList[i][0], "/", 1  )  )
446                        testStr = W_ElementList[index][4] + CS_XPath_NS("/"+W_ElementList[i][3])
447                        XMLlistXpath(fileID,testStr,nsStr)
448                        WAVE/T W_listXPath
449                        SWITCH(numpnts(W_listXPath))
450                                CASE 0:                 // What?  Can't find the node we just found?
451                                        PRINT numpnts(W_listXPath), testStr, " !!! WARNING:  <Can't find the node we just found> in CS_registerNameSpaces()"
452                                        BREAK
453                                CASE 1:                 // Only one node with this element
454                                        W_ElementList[i][4] = testStr
455                                        BREAK
456                                DEFAULT:                        // Multiple elements match this node; need to use indices
457                                        // PRINT numpnts(W_listXPath), testStr
458                                        FOR (j = 0; j < numpnts(W_listXPath); j += 1)
459                                                index = CS_findElementIndex(  W_listXPath[j]  )
460                                                W_ElementList[index][4] = testStr + "[" + num2str(j+1) + "]"
461                                        ENDFOR
462                                        BREAK
463                        ENDSWITCH
464                ENDIF
465        ENDFOR
466        // PRINT StringByKey("schemaLocation", W_ElementList[0][2])
467        //
468        // <code comes here>
469        RETURN(0)
470END
471
472// ==================================================================
473
474FUNCTION/S CS_findLast(src, matchStr, leftSide)
475        STRING src
476        STRING matchStr
477        VARIABLE leftSide
478        // given a source string such as:   /*/*/*[7]/*/*[39]/*[2]
479        // return the part on the $leftSide if the $matchStr
480        // Examples:
481        //       CS_findLast( "/*/*/*[7]/*/*[39]/*[2]"  , "/", 1)                 /*/*/*[7]/*/*[39]
482        //       CS_findLast( "/*/*/*[7]/*/*[39]/*[2]"  , "/", 0)                 /*[2]
483        //       CS_findLast( "/*/*/*[7]/*/*[39]/*[2]"  , "[", 1)                 /*/*/*[7]/*/*[39]/*
484        //       CS_findLast( "/*/*/*[7]/*/*[39]/*[2]"  , "[", 0)                 [2]
485        VARIABLE i
486        STRING result="", test
487        FOR (i=strlen(src)-1; i >= 0; i -= 1)
488                IF (stringmatch(src[i], matchStr ))
489                        IF (leftSide)
490                                result = src[0,i-1]
491                        ELSE
492                                result = src[i,Inf]
493                        ENDIF
494                        RETURN(result)
495                ENDIF
496        ENDFOR
497        RETURN(result)
498END
499
500// ==================================================================
501
502FUNCTION/S CS_XPath_NS(simpleStr)
503        // namespaces complicate the XPath description
504        // this function adds namespace info as necessary to simpleStr (an XPath)
505        STRING simpleStr
506        SVAR nsPre
507        STRING result = "", thisChar, lastChar = ""
508        VARIABLE i
509        FOR (i = 0; i < strlen(simpleStr); i += 1)
510                //PRINT simpleStr[i]
511                thisChar = simpleStr[i]
512                IF ( CmpStr(lastChar, "/") == 0 )
513                        STRSWITCH (thisChar)
514                                CASE "/":
515                                CASE ".":
516                                CASE "@":
517                                        BREAK
518                                DEFAULT:
519                                        result += nsPre
520                        ENDSWITCH
521                ENDIF
522                result += thisChar
523                lastChar = thisChar
524        ENDFOR
525        RETURN(result)
526END
527
528// ==================================================================
529
530FUNCTION/S CS_XmlStrFmXpath(fileID, prefix, value)
531        VARIABLE fileID
532        STRING prefix, value
533        SVAR nsStr
534        STRING result
535        result = TrimWS(XmlStrFmXpath(fileID, prefix + CS_XPath_NS(value), nsStr, ""))
536        RETURN( result )
537END
538
539// ==================================================================
540
541FUNCTION CS_simpleXmlWaveFmXpath(fileID, prefix, value)
542        VARIABLE fileID
543        STRING prefix, value
544        SVAR nsStr
545        XMLwaveFmXpath(fileID, prefix + CS_XPath_NS(value), nsStr, " ")
546        // output: M_xmlContent  W_xmlContentNodes
547END
548
549// ==================================================================
550
551FUNCTION/S CS_correctedXpathStr(indexPath)
552        STRING indexPath
553        VARIABLE index
554        STRING absolutePath
555        WAVE/T          W_ElementList
556        index  =  CS_findElementIndex(indexPath)
557        absolutePath = W_ElementList[index][4]  // use corrected XPath string
558        RETURN(absolutePath)
559END
560
561// ==================================================================
562
563FUNCTION CS_simpleXmlListXpath(fileID, prefix, value)
564        VARIABLE fileID
565        STRING prefix, value
566        SVAR nsStr
567        XMLlistXpath(fileID, prefix + CS_XPath_NS(value), nsStr)                // output: W_listXPath
568END
569
570// ==================================================================
571
572Function/T   TrimWS(str)
573    // TrimWhiteSpace (code from Jon Tischler)
574    String str
575    str = TrimWSL(str)
576    str = TrimWSR(str)
577    return str
578End
579
580// ==================================================================
581
582Function/T   TrimWSL(str)
583    // TrimWhiteSpaceLeft (code from Jon Tischler)
584    String str
585    Variable i, N=strlen(str)
586    for (i=0;char2num(str[i])<=32 && i<N;i+=1)    // find first non-white space
587    endfor
588    return str[i,Inf]
589End
590
591// ==================================================================
592
593Function/T   TrimWSR(str)
594    // TrimWhiteSpaceRight (code from Jon Tischler)
595    String str
596    Variable i
597    for (i=strlen(str)-1; char2num(str[i])<=32 && i>=0; i-=1)    // find last non-white space
598    endfor
599    return str[0,i]
600End
601
602// ==================================================================
603
604FUNCTION CS_updateWaveNote(wavName, key, value)
605        STRING wavName, key, value
606        STRING wavenote
607        wavenote = ReplaceStringByKey(key, note($wavName), value)
608        Note /K $wavName, wavenote
609END
610
611// ==================================================================
612
613FUNCTION CS_1i_extractIdataColumn2Wave(fileID, basePath, colName, wavName)
614        //
615        // this function pulls one column of data from each <Idata> element
616        // easier to write this as a function than debug it all the times it is needed
617        //
618        VARIABLE fileID
619        STRING basePath, colName, wavName
620        STRING unit
621        WAVE/T metadata
622        VARIABLE i, numPts
623
624        //      Q values come out in multiple columns. Different nodes means different columns in M_xmlcontent
625        //      Multiple values in the SAME node (i.e. a vector) get put in different rows.
626        //      We are therefore going to transpose the wave
627        //      (Based on example from Andrew R.J. Nelson.)
628        CS_simpleXmlWaveFmXpath(fileID, basePath, "//Idata/" + colName)
629        WAVE/T  M_xmlcontent, W_xmlcontentnodes
630        numPts = numpnts(M_XMLcontent)
631        IF (numPts > 0)
632                MatrixTranspose M_XMLcontent
633                MAKE/O/D/N=(numpnts(M_XMLcontent)) $wavName = str2num(M_xmlcontent[p][0])
634                // don't forget the units!  Assume that all rows have the same "unit" as in the first row.
635                unit = CS_XmlStrFmXpath(fileID, basePath, "/Idata[1]/"+colName+"/@unit")
636                SetScale d 0, 1, unit, $wavName                         // update the wave's "UNITS" string
637                SetScale x 0, 1, unit, $wavName                         // put it here, too, for the Data Browser
638                // put unit directly into wavenote of _this_ wave
639                CS_updateWaveNote(wavName, "unit", unit)                // put UNIT in wavenote
640                // store all the metadata in the wavenote (for now, at least)
641                FOR (i = 0; i < DimSize(metadata, 0); i += 1)
642                        IF (strlen(metadata[i][2]) > 0)
643                                // only add defined metadata to the wavenote
644                                CS_updateWaveNote(wavName, metadata[i][0], metadata[i][2])
645                        ENDIF
646                ENDFOR
647        ELSE
648                // did not find any data
649                // no need to apply special handling here; do that in the caller
650        ENDIF
651        //IF (numPts)
652        //      PRINT "\t\t\t\t" + wavName + ": found " + num2str(numPts) + " points"
653        //ENDIF
654        RETURN(numPts)
655END
656
657// ==================================================================
658
659FUNCTION CS_1i_extractSasData(fileID, SASdataPath, SASdata_folder)
660        //
661        // extract data from the SASdata/Idata block in a canSAS1d/v1.0 XML file
662        //  (1i in the function name signifies this is a function that supports INPUT from version 1.0 XML files)
663        //
664        //      returns:
665        //              0       no error
666        //              1       number of points in waves is not the same as Qsas wave
667        //
668        VARIABLE fileID
669        STRING SASdataPath, SASdata_folder
670        WAVE/T metadata
671        VARIABLE numPts, numQ
672        SVAR errorMsg
673
674        // extract each Idata column into the waves: QQ, II, Qdev, Idev [Qmean] [Qfwhm] [Shadowfactor]
675        // ignore the return codes here, check below
676        numQ    = CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "Q",                               "Qsas")
677        IF (numQ != CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "I",                             "Isas"))
678                errorMsg = "number of points in Qsas and Isas waves are not identical"
679                RETURN(1)
680        ENDIF
681        IF (numQ != CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "Qdev",                  "Qdev"))
682                errorMsg = "number of points in Qsas and Qdev waves is not identical"
683                RETURN(1)
684        ENDIF
685        IF (numQ != CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "Idev",                  "Idev"))
686                errorMsg = "number of points in Qsas and Idev waves is not identical"
687                RETURN(1)
688        ENDIF
689        numPts = CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "Qmean",            "Qmean")
690        IF (numPts && (numQ != numPts) )
691                errorMsg = "number of points in Qsas and Qmean waves is not identical"
692                RETURN(1)
693        ENDIF
694        numPts = CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "Qfwhm",            "Qfwhm")
695        IF (numPts && (numQ != numPts) )
696                errorMsg = "number of points in Qsas and Qfwhm waves is not identical"
697                RETURN(1)
698        ENDIF
699        numPts = CS_1i_extractIdataColumn2Wave(fileID, SASdataPath, "Shadowfactor",     "Shadowfactor")
700        IF (numPts && (numQ != numPts) )
701                errorMsg = "number of points in Qsas and Shadowfactor waves is not identical"
702                RETURN(1)
703        ENDIF
704
705        PRINT "\t\t\t\t found " + num2str(numpnts(Qsas)) + " points"
706
707        // move the waves to the sample folder
708        // !!!!! Missing Qsas, Isas, Qdev, and/or Idev are a broken data set
709        //              This should produce an exception.  Should have been trapped by numPts tests.
710        //              Best to return an error code but the caller chain is not ready to pass that to the top level, yet.
711        MoveWave Qsas, $SASdata_folder                  // required wave
712        MoveWave Isas, $SASdata_folder                  // required wave
713        MoveWave Qdev, $SASdata_folder                  // required wave
714        MoveWave Idev, $SASdata_folder                  // required wave
715        IF (exists("Qmean") == 1)
716                MoveWave Qmean, $SASdata_folder // optional wave
717        ENDIF
718        IF (exists("Qfwhm") == 1)
719                MoveWave Qfwhm, $SASdata_folder // optional wave
720        ENDIF
721        IF (exists("ShadowFactor") == 1)
722                MoveWave ShadowFactor, $SASdata_folder  // optional wave
723        ENDIF
724        IF (exists("metadata") == 1)
725                Duplicate/O metadata, $SASdata_folder + "metadata"
726        ENDIF
727        RETURN(0)                       // no error
728END
729
730
731// ==================================================================
732// ==================================================================
733// ==================================================================
734
735
736FUNCTION prj_grabMyXmlData()
737        STRING srcDir = "root:Packages:CS_XMLreader"
738        STRING destDir = "root:PRJ_canSAS"
739        STRING srcFolder, destFolder, theFolder
740        Variable i
741        NewDataFolder/O  $destDir               // for all my imported data
742        FOR ( i = 0; i < CountObjects(srcDir, 4) ; i += 1 )
743                theFolder = GetIndexedObjName(srcDir, 4, i)
744                srcFolder = srcDir + ":" + theFolder
745                destFolder = destDir + ":" + theFolder
746                // PRINT srcFolder, destFolder
747                IF (DataFolderExists(destFolder))
748                        // !!!!!!!!!!!!!!!!! NOTE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
749                        // need to find unique name for destination
750                        // Persons who implement this properly should be more elegant
751                        // For now, I will blast the existing and proceed blindly.
752                        KillDataFolder/Z  $destFolder           // clear out any previous work
753                        DuplicateDataFolder $srcFolder, $destFolder
754                ELSE
755                        DuplicateDataFolder $srcFolder, $destFolder
756                ENDIF
757        ENDFOR
758END
759
760FUNCTION prjTest_cansas1d()
761        // unit tests for the routines under prj-readXML.ipf
762        STRING theFile
763        STRING fList = ""
764        VARIABLE i
765        // build a table of test data sets
766        fList = AddListItem("elmo.xml",                                 fList, ";", Inf)                // non-existent file
767        fList = AddListItem("cansasXML.ipf",                    fList, ";", Inf)                // this file (should fail on XML parsing)
768        fList = AddListItem("book.xml",                                 fList, ";", Inf)                // good XML example file but not canSAS, not even close
769        fList = AddListItem("bimodal-test1.xml",                fList, ";", Inf)                // simple dataset
770        fList = AddListItem("bimodal-test2-vector.xml", fList, ";", Inf)                // version 2.0 file (no standard yet)
771        fList = AddListItem("test.xml",                                 fList, ";", Inf)                // cs_collagen.xml with no namespace
772        fList = AddListItem("test2.xml",                                fList, ";", Inf)                // version 2.0 file (no standard yet)
773        fList = AddListItem("ill_sasxml_example.xml",   fList, ";", Inf)                // from canSAS 2007 meeting, reformatted
774        fList = AddListItem("isis_sasxml_example.xml",  fList, ";", Inf)                // from canSAS 2007 meeting, reformatted
775        fList = AddListItem("r586.xml",                                         fList, ";", Inf)                // from canSAS 2007 meeting, reformatted
776        fList = AddListItem("r597.xml",                                         fList, ";", Inf)                // from canSAS 2007 meeting, reformatted
777        fList = AddListItem("cs_collagen.xml",                  fList, ";", Inf)                // another simple dataset, bare minimum info
778        fList = AddListItem("cs_collagen_full.xml",             fList, ";", Inf)                // more Q range than previous
779        fList = AddListItem("cs_af1410.xml",                    fList, ";", Inf)                // multiple SASentry and SASdata elements
780        fList = AddListItem("1998spheres.xml",                  fList, ";", Inf)                // 2 SASentry, few thousand data points each
781        fList = AddListItem("does-not-exist-file.xml",          fList, ";", Inf)                // non-existent file
782        // try to load each data set in the table
783        FOR ( i = 0; i < ItemsInList(fList) ; i += 1 )
784                theFile = StringFromList(i, fList)                                      // walk through all test files
785                // PRINT "file: ", theFile
786                pathInfo home
787                //IF (CS_XmlReader(theFile) == 0)                                       // did the XML reader return without an error code?
788                IF (CS_XmlReader(ParseFilePath(5,S_path,"*",0,0) + theFile) == 0)    // did the XML reader return without an error code?
789                        prj_grabMyXmlData()                                             // move the data to my directory
790                ENDIF
791        ENDFOR
792END
793
794// ==================================================================
795
796FUNCTION PRJ_quick(fileID)
797        VARIABLE fileID
798        WAVE/T W_ElementList, W_listXPath
799        SVAR nsStr
800
801        XMLlistXpath(fileID, "/cs:SASroot/cs:SASentry/cs:Title", nsStr)         // output: W_listXPath
802        print W_listXPath[0]
803        XMLlistXpath(fileID, "//cs:Idata[4]", nsStr)            // output: W_listXPath
804        print W_listXPath[0]
805        XMLlistXpath(fileID, "//cs:Idata*", nsStr)              // output: W_listXPath
806        print W_listXPath[0]
807        XMLlistXpath(fileID, "//cs:Idata", nsStr)               // output: W_listXPath
808        print W_listXPath[0]
809        XMLlistXpath(fileID, "/cs:SASroot/cs:SASentry/cs:SASdata/cs:Idata[4]", nsStr)           // output: W_listXPath
810        print W_listXPath[0]
811        XMLlistXpath(fileID, "/*/@*", "")               // output: W_listXPath
812        print W_listXPath[0]
813        PRINT TrimWS(XmlStrFmXpath(fileID, "/*/@version", "", ""))
814        PRINT TrimWS(XmlStrFmXpath(fileID, "/*/@xmlns", "", ""))
815        PRINT StringByKey("schemaLocation", W_ElementList[0][2])
816
817        //CS_simpleXmlListXpath(fileID, "", "/SASroot//SASentry")
818        //WAVE/T        W_listXPath
819        //DUPLICATE/O/T   W_listXPath, SASentryList
820
821        //<SASroot version="1.0"
822        //      xmlns="http://www.smallangles.net/cansas1d"
823        //      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
824        //      xsi:schemaLocation="http://www.smallangles.net/cansas1d/ http://www.smallangles.net/cansas1d/1.0/cansas1d.xsd"
825        //      >
826
827
828END
Note: See TracBrowser for help on using the repository browser.