(require (lib "url.ss" "net")) (require (lib "uri-codec.ss" "net")) (require (lib "ssax.ss" "ssax")) (require (lib "sxpath.ss" "sxml")) (require (lib "string.ss" "srfi" "13")) (require (lib "test.ss" "schemeunit")) ; A forecast for a single day is a ; string int int int string int string (define-struct forecast (day temp-high temp-low morning-chance morning-outlook evening-chance evening-outlook)) ; How many days to extract a forecast. We default to a seven day forecast (define days-in-forecast 7) ; The base URL for the weather forecast XML feed. (define w-url (string->url "http://www2.wrh.noaa.gov/cgi-bin/dwf")) ; A list of post parameters to the service. These are the magic values ; for a 7 day forecast. (define w-alist '(("outFormat" . "xml") ("duration" . "168hr") ("interval" . "12") ("citylist" . "SLC Airport,40.785,-111.9277") ("city" . "Go") ("longitude" . "") ("latitude" . "") ("ZOOMLEVEL" . "1") ("XSTART" . "") ("YSTART" . "") ("XC" . "") ("YC" . "") ("X" . "") ("Y" . "") ("siteID" . "SLC"))) ; The forecast as sxml expression. Posts to the URL with the form parameters in w-alist, ; reads the output from the port returned from SSAX:XML->SXML. (define f-sxml (SSAX:XML->SXML (post-pure-port w-url (alist->form-urlencoded w-alist)) '())) ; int->string where xpath? ; Return the root XPath expression for the forecast for the day given as d. (define (xpath-for-day d) (let ((root "//forecastDay")) (string-append root "[" (number->string d) "]" "//"))) ; A list of the child xpaths for each part of the forecast. Concentating the nth value in ; this list the with (forecast-day-xpath d) will give the XPath expression to fetch the ; XML element whose text contains the value need for the nth forecast struct member. (define f-xpath-children '("validDate" "maxTemp" "minTemp" "period[1]/pop" "period[1]/wx" "period[2]/pop" "period[2]/wx")) (define (f-paths d) (map (lambda (s) (string-append (xpath-for-day d) s "/text()")) f-xpath-children)) ; string where xpath? -> string ; Extract the context of an SXML element. (define (string-from-xpath xpath sxml) (string-trim-both (car ((sxpath xpath) f-sxml)) (char-set-union char-set:whitespace char-set:iso-control))) ; sxml -> forecast ; Make a forecast struct from a sxml element. (define (build-forecast d fs) (let ((f-values (map (lambda (p) (string-from-xpath p fs)) (f-paths d)))) (apply make-forecast f-values))) ; void -> list of forecast ; build a list that is days-in-forecast long. (define (build-forecast-list f-sxml) (let ((bfl-loop (lambda (d) (cond ((> d f-days) '()) (else (cons (build-forecast d) (build-forecast-list (+ 1 d)))))))) ; Okay, build the list. Call the recursive loop and return the list. (build-forecast-list 1))) ;Tests ; This is a bunch of shared state. TO-DO: Move this to weather-forecast-test.scm (define t-port (open-input-file "C:\\ndykman\\cs6520\\WeatherProject\\forecast-test.xml")) (define t-sxml (SSAX:XML->SXML t-port '())) (define t-forecast (build-forecast 2 t-sxml)) (define forecast-tests (make-test-suite "Tests for weather-forecast.scm" (make-test-case "f-paths" (assert string= (caddr (f-paths 2)) "//forecastDay[2]//minTemp/text()")) (make-test-case "sfp test 1" (assert string= (string-from-xpath "//forecastDay[2]//minTemp/text()" t-sxml) "31")) (make-test-case "sfp test 2" (assert string= (string-from-xpath "//forecastDay[2]//period[1]/pop/text()" t-sxml) "28"))))