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

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

two more tests, handle pathologies in SASdata, added pragma for version=1.01 to cansasXML.ipf

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