json.tcl

Go to the documentation of this file.
00001 /* */
00002 /*    JSON parser for Tcl.*/
00003 /* */
00004 /*    See http://www.json.org/ && http://www.ietf.org/rfc/rfc4627.txt*/
00005 /* */
00006 /*    Copyright 2006 ActiveState Software Inc.*/
00007 /* */
00008 /*    $Id: json.tcl,v 1.2 2006/08/25 23:19:53 hobbs Exp $*/
00009 /* */
00010 
00011 if {$::tcl_version < 8.5} {
00012     package require dict
00013 }
00014 
00015 package provide json 1.0
00016 
00017 namespace json {}
00018 
00019 ret  json::getc (optional txtvar =txt) {
00020     # pop single char off the front of the text
00021     upvar 1 $txtvar txt
00022     if {$txt eq ""} {
00023         return -code error "unexpected end of text"
00024     }
00025 
00026     set c [string index $txt 0]
00027     set txt [string range $txt 1 end]
00028     return $c
00029 }
00030 
00031 ret  json::json2dict (type txt) {
00032     return [_json2dict]
00033 }
00034 
00035 ret  json::_json2dict (optional txtvar =txt) {
00036     upvar 1 $txtvar txt
00037 
00038     set state TOP
00039 
00040     set txt [string trimleft $txt]
00041     while {$txt ne ""} {
00042         set c [string index $txt 0]
00043 
00044         # skip whitespace
00045         while {[string is space $c]} {
00046             getc
00047             set c [string index $txt 0]
00048         }
00049 
00050     if {$c eq "\{"} {
00051         # object
00052         switch -- $state {
00053         TOP {
00054             # we are dealing with an Object
00055             getc
00056             set state OBJECT
00057             set dictVal [dict create]
00058         }
00059         VALUE {
00060             # this object element's value is an Object
00061             dict set dictVal $name [_json2dict]
00062             set state COMMA
00063         }
00064         LIST {
00065             # next element of list is an Object
00066             lappend listVal [_json2dict]
00067             set state COMMA
00068         }
00069         default {
00070             return -code error "unexpected open brace in $state mode"
00071         }
00072         }
00073     } elseif {$c eq "\}"} {
00074         getc
00075         if {$state ne "OBJECT" && $state ne "COMMA"} {
00076         return -code error "unexpected close brace in $state mode"
00077         }
00078         return $dictVal
00079     } elseif {$c eq ":"} {
00080         # name separator
00081         getc
00082 
00083         if {$state eq "COLON"} {
00084         set state VALUE
00085         } else {
00086         return -code error "unexpected colon in $state mode"
00087         }
00088     } elseif {$c eq ","} {
00089         # element separator
00090         if {$state eq "COMMA"} {
00091         getc
00092         if {[info exists listVal]} {
00093             set state LIST
00094         } elseif {[info exists dictVal]} {
00095             set state OBJECT
00096         }
00097         } else {
00098         return -code error "unexpected comma in $state mode"
00099         }
00100     } elseif {$c eq "\""} {
00101         # string
00102         # capture quoted string with backslash sequences
00103         set reStr {(?:(?:\")(?:[^\\\"]*(?:\\.[^\\\"]*)*)(?:\"))}
00104         set string ""
00105         if {![regexp $reStr $txt string]} {
00106         set txt [string replace $txt 32 end ...]
00107         return -code error "invalid formatted string in $txt"
00108         }
00109         set txt [string range $txt [string length $string] end]
00110         # chop off outer ""s and substitute backslashes
00111         # This does more than the RFC-specified backslash sequences,
00112         # but it does cover them all
00113         set string [subst -nocommand -novariable \
00114                 [string range $string 1 end-1]]
00115 
00116         switch -- $state {
00117         TOP {
00118             return $string
00119         }
00120         OBJECT {
00121             set name $string
00122             set state COLON
00123         }
00124         LIST {
00125             lappend listVal $string
00126             set state COMMA
00127         }
00128         VALUE {
00129             dict set dictVal $name $string
00130             unset name
00131             set state COMMA
00132         }
00133         }
00134     } elseif {$c eq "\["} {
00135         # JSON array == Tcl list
00136         switch -- $state {
00137         TOP {
00138             getc
00139             set state LIST
00140         }
00141         LIST {
00142             lappend listVal [_json2dict]
00143             set state COMMA
00144         }
00145         VALUE {
00146             dict set dictVal $name [_json2dict]
00147             set state COMMA
00148         }
00149         default {
00150             return -code error "unexpected open bracket in $state mode"
00151         }
00152         }
00153     } elseif {$c eq "\]"} {
00154         # end of list
00155         getc
00156         if {![info exists listVal]} {
00157         #return -code error "unexpected close bracket in $state mode"
00158         # must be an empty list
00159         return ""
00160         }
00161 
00162         return $listVal
00163     } elseif {0 && $c eq "/"} {
00164         # comment
00165         # XXX: Not in RFC 4627
00166         getc
00167         set c [getc]
00168         switch -- $c {
00169         / {
00170             # // comment form
00171             set i [string first "\n" $txt]
00172             if {$i == -1} {
00173             set txt ""
00174             } else {
00175             set txt [string range $txt [incr i] end]
00176             }
00177         }
00178         * {
00179             # /* comment */ form
00180             getc
00181             set i [string first "*/" $txt]
00182             if {$i == -1} {
00183             return -code error "incomplete /* comment"
00184             } else {
00185             set txt [string range $txt [incr i] end]
00186             }
00187         }
00188         default {
00189             return -code error "unexpected slash in $state mode"
00190         }
00191         }
00192     } elseif {[string match {[-0-9]} $c]} {
00193         # one last check for a number, no leading zeros allowed,
00194         # but it may be 0.xxx
00195         string is double -failindex last $txt
00196         if {$last > 0} {
00197         set num [string range $txt 0 [expr {$last - 1}]]
00198         set txt [string range $txt $last end]
00199 
00200         switch -- $state {
00201             TOP {
00202             return $num
00203             }
00204             LIST {
00205             lappend listVal $num
00206             set state COMMA
00207             }
00208             VALUE {
00209             dict set dictVal $name $num
00210             set state COMMA
00211             }
00212             default {
00213             getc
00214             return -code error "unexpected number '$c' in $state mode"
00215             }
00216         }
00217         } else {
00218         getc
00219         return -code error "unexpected '$c' in $state mode"
00220         }
00221     } elseif {[string match {[ftn]} $c]
00222           && [regexp {^(true|false|null)} $txt val]} {
00223         # bare word value: true | false | null
00224         set txt [string range $txt [string length $val] end]
00225 
00226         switch -- $state {
00227         TOP {
00228             return $val
00229         }
00230         LIST {
00231             lappend listVal $val
00232             set state COMMA
00233         }
00234         VALUE {
00235             dict set dictVal $name $val
00236             set state COMMA
00237         }
00238         default {
00239             getc
00240             return -code error "unexpected '$c' in $state mode"
00241         }
00242         }
00243     } else {
00244         # error, incorrect format or unexpected end of text
00245         return -code error "unexpected '$c' in $state mode"
00246     }
00247     }
00248 }
00249 
00250 ret  json::dict2json (type dictVal) {
00251     # XXX: Currently this API isn't symmetrical, as to create proper
00252     # XXX: JSON text requires type knowledge of the input data
00253     set json ""
00254 
00255     dict for {key val} $dictVal {
00256     # key must always be a string, val may be a number, string or
00257     # bare word (true|false|null)
00258     if {0 && ![string is double -strict $val]
00259         && ![regexp {^(?:true|false|null)$} $val]} {
00260         set val "\"$val\""
00261     }
00262         append json "\"$key\": $val," \n
00263     }
00264 
00265     return "\{${json}\}"
00266 }
00267 
00268 ret  json::list2json (type listVal) {
00269     return "\[$[join $listVal ,]\]"
00270 }
00271 
00272 ret  json::string2json (type str) {
00273     return "\"$str\""
00274 }
00275 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1