311 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
| import string
 | |
| import types
 | |
| 
 | |
| ##    json.py implements a JSON (http://json.org) reader and writer.
 | |
| ##    Copyright (C) 2005  Patrick D. Logan
 | |
| ##    Contact mailto:patrickdlogan@stardecisions.com
 | |
| ##
 | |
| ##    This library is free software; you can redistribute it and/or
 | |
| ##    modify it under the terms of the GNU Lesser General Public
 | |
| ##    License as published by the Free Software Foundation; either
 | |
| ##    version 2.1 of the License, or (at your option) any later version.
 | |
| ##
 | |
| ##    This library is distributed in the hope that it will be useful,
 | |
| ##    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| ##    Lesser General Public License for more details.
 | |
| ##
 | |
| ##    You should have received a copy of the GNU Lesser General Public
 | |
| ##    License along with this library; if not, write to the Free Software
 | |
| ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| 
 | |
| 
 | |
| class _StringGenerator(object):
 | |
|         def __init__(self, string):
 | |
|                 self.string = string
 | |
|                 self.index = -1
 | |
|         def peek(self):
 | |
|                 i = self.index + 1
 | |
|                 if i < len(self.string):
 | |
|                         return self.string[i]
 | |
|                 else:
 | |
|                         return None
 | |
|         def next(self):
 | |
|                 self.index += 1
 | |
|                 if self.index < len(self.string):
 | |
|                         return self.string[self.index]
 | |
|                 else:
 | |
|                         raise StopIteration
 | |
|         def all(self):
 | |
|                 return self.string
 | |
| 
 | |
| class WriteException(Exception):
 | |
|     pass
 | |
| 
 | |
| class ReadException(Exception):
 | |
|     pass
 | |
| 
 | |
| class JsonReader(object):
 | |
|     hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
 | |
|     escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}
 | |
| 
 | |
|     def read(self, s):
 | |
|         self._generator = _StringGenerator(s)
 | |
|         result = self._read()
 | |
|         return result
 | |
| 
 | |
|     def _read(self):
 | |
|         self._eatWhitespace()
 | |
|         peek = self._peek()
 | |
|         if peek is None:
 | |
|             raise ReadException, "Nothing to read: '%s'" % self._generator.all()
 | |
|         if peek == '{':
 | |
|             return self._readObject()
 | |
|         elif peek == '[':
 | |
|             return self._readArray()            
 | |
|         elif peek == '"':
 | |
|             return self._readString()
 | |
|         elif peek == '-' or peek.isdigit():
 | |
|             return self._readNumber()
 | |
|         elif peek == 't':
 | |
|             return self._readTrue()
 | |
|         elif peek == 'f':
 | |
|             return self._readFalse()
 | |
|         elif peek == 'n':
 | |
|             return self._readNull()
 | |
|         elif peek == '/':
 | |
|             self._readComment()
 | |
|             return self._read()
 | |
|         else:
 | |
|             raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
 | |
| 
 | |
|     def _readTrue(self):
 | |
|         self._assertNext('t', "true")
 | |
|         self._assertNext('r', "true")
 | |
|         self._assertNext('u', "true")
 | |
|         self._assertNext('e', "true")
 | |
|         return True
 | |
| 
 | |
|     def _readFalse(self):
 | |
|         self._assertNext('f', "false")
 | |
|         self._assertNext('a', "false")
 | |
|         self._assertNext('l', "false")
 | |
|         self._assertNext('s', "false")
 | |
|         self._assertNext('e', "false")
 | |
|         return False
 | |
| 
 | |
|     def _readNull(self):
 | |
|         self._assertNext('n', "null")
 | |
|         self._assertNext('u', "null")
 | |
|         self._assertNext('l', "null")
 | |
|         self._assertNext('l', "null")
 | |
|         return None
 | |
| 
 | |
|     def _assertNext(self, ch, target):
 | |
|         if self._next() != ch:
 | |
|             raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
 | |
| 
 | |
|     def _readNumber(self):
 | |
|         isfloat = False
 | |
|         result = self._next()
 | |
|         peek = self._peek()
 | |
|         while peek is not None and (peek.isdigit() or peek == "."):
 | |
|             isfloat = isfloat or peek == "."
 | |
|             result = result + self._next()
 | |
|             peek = self._peek()
 | |
|         try:
 | |
|             if isfloat:
 | |
|                 return float(result)
 | |
|             else:
 | |
|                 return int(result)
 | |
|         except ValueError:
 | |
|             raise ReadException, "Not a valid JSON number: '%s'" % result
 | |
| 
 | |
|     def _readString(self):
 | |
|         result = ""
 | |
|         assert self._next() == '"'
 | |
|         try:
 | |
|             while self._peek() != '"':
 | |
|                 ch = self._next()
 | |
|                 if ch == "\\":
 | |
|                     ch = self._next()
 | |
|                     if ch in 'brnft':
 | |
|                         ch = self.escapes[ch]
 | |
|                     elif ch == "u":
 | |
|                         ch4096 = self._next()
 | |
|                         ch256  = self._next()
 | |
|                         ch16   = self._next()
 | |
|                         ch1    = self._next()
 | |
|                         n = 4096 * self._hexDigitToInt(ch4096)
 | |
|                         n += 256 * self._hexDigitToInt(ch256)
 | |
|                         n += 16  * self._hexDigitToInt(ch16)
 | |
|                         n += self._hexDigitToInt(ch1)
 | |
|                         ch = unichr(n)
 | |
|                     elif ch not in '"/\\':
 | |
|                         raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
 | |
|                 result = result + ch
 | |
|         except StopIteration:
 | |
|             raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
 | |
|         assert self._next() == '"'
 | |
|         return result
 | |
| 
 | |
|     def _hexDigitToInt(self, ch):
 | |
|         try:
 | |
|             result = self.hex_digits[ch.upper()]
 | |
|         except KeyError:
 | |
|             try:
 | |
|                 result = int(ch)
 | |
|             except ValueError:
 | |
|                  raise ReadException, "The character %s is not a hex digit." % ch
 | |
|         return result
 | |
| 
 | |
|     def _readComment(self):
 | |
|         assert self._next() == "/"
 | |
|         second = self._next()
 | |
|         if second == "/":
 | |
|             self._readDoubleSolidusComment()
 | |
|         elif second == '*':
 | |
|             self._readCStyleComment()
 | |
|         else:
 | |
|             raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
 | |
| 
 | |
|     def _readCStyleComment(self):
 | |
|         try:
 | |
|             done = False
 | |
|             while not done:
 | |
|                 ch = self._next()
 | |
|                 done = (ch == "*" and self._peek() == "/")
 | |
|                 if not done and ch == "/" and self._peek() == "*":
 | |
|                     raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
 | |
|             self._next()
 | |
|         except StopIteration:
 | |
|             raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
 | |
| 
 | |
|     def _readDoubleSolidusComment(self):
 | |
|         try:
 | |
|             ch = self._next()
 | |
|             while ch != "\r" and ch != "\n":
 | |
|                 ch = self._next()
 | |
|         except StopIteration:
 | |
|             pass
 | |
| 
 | |
|     def _readArray(self):
 | |
|         result = []
 | |
|         assert self._next() == '['
 | |
|         done = self._peek() == ']'
 | |
|         while not done:
 | |
|             item = self._read()
 | |
|             result.append(item)
 | |
|             self._eatWhitespace()
 | |
|             done = self._peek() == ']'
 | |
|             if not done:
 | |
|                 ch = self._next()
 | |
|                 if ch != ",":
 | |
|                     raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
 | |
|         assert ']' == self._next()
 | |
|         return result
 | |
| 
 | |
|     def _readObject(self):
 | |
|         result = {}
 | |
|         assert self._next() == '{'
 | |
|         done = self._peek() == '}'
 | |
|         while not done:
 | |
|             key = self._read()
 | |
|             if type(key) is not types.StringType:
 | |
|                 raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
 | |
|             self._eatWhitespace()
 | |
|             ch = self._next()
 | |
|             if ch != ":":
 | |
|                 raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
 | |
|             self._eatWhitespace()
 | |
|             val = self._read()
 | |
|             result[key] = val
 | |
|             self._eatWhitespace()
 | |
|             done = self._peek() == '}'
 | |
|             if not done:
 | |
|                 ch = self._next()
 | |
|                 if ch != ",":
 | |
|                     raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
 | |
|         assert self._next() == "}"
 | |
|         return result
 | |
| 
 | |
|     def _eatWhitespace(self):
 | |
|         p = self._peek()
 | |
|         while p is not None and p in string.whitespace or p == '/':
 | |
|             if p == '/':
 | |
|                 self._readComment()
 | |
|             else:
 | |
|                 self._next()
 | |
|             p = self._peek()
 | |
| 
 | |
|     def _peek(self):
 | |
|         return self._generator.peek()
 | |
| 
 | |
|     def _next(self):
 | |
|         return self._generator.next()
 | |
| 
 | |
| class JsonWriter(object):
 | |
|         
 | |
|     def _append(self, s):
 | |
|         self._results.append(s)
 | |
| 
 | |
|     def write(self, obj, escaped_forward_slash=False):
 | |
|         self._escaped_forward_slash = escaped_forward_slash
 | |
|         self._results = []
 | |
|         self._write(obj)
 | |
|         return "".join(self._results)
 | |
| 
 | |
|     def _write(self, obj):
 | |
|         ty = type(obj)
 | |
|         if ty is types.DictType:
 | |
|             n = len(obj)
 | |
|             self._append("{")
 | |
|             for k, v in obj.items():
 | |
|                 self._write(k)
 | |
|                 self._append(":")
 | |
|                 self._write(v)
 | |
|                 n = n - 1
 | |
|                 if n > 0:
 | |
|                     self._append(",")
 | |
|             self._append("}")
 | |
|         elif ty is types.ListType or ty is types.TupleType:
 | |
|             n = len(obj)
 | |
|             self._append("[")
 | |
|             for item in obj:
 | |
|                 self._write(item)
 | |
|                 n = n - 1
 | |
|                 if n > 0:
 | |
|                     self._append(",")
 | |
|             self._append("]")
 | |
|         elif ty is types.StringType or ty is types.UnicodeType:
 | |
|             self._append('"')
 | |
|             obj = obj.replace('\\', r'\\')
 | |
|             if self._escaped_forward_slash:
 | |
|                 obj = obj.replace('/', r'\/')
 | |
|             obj = obj.replace('"', r'\"')
 | |
|             obj = obj.replace('\b', r'\b')
 | |
|             obj = obj.replace('\f', r'\f')
 | |
|             obj = obj.replace('\n', r'\n')
 | |
|             obj = obj.replace('\r', r'\r')
 | |
|             obj = obj.replace('\t', r'\t')
 | |
|             self._append(obj)
 | |
|             self._append('"')
 | |
|         elif ty is types.IntType or ty is types.LongType:
 | |
|             self._append(str(obj))
 | |
|         elif ty is types.FloatType:
 | |
|             self._append("%f" % obj)
 | |
|         elif obj is True:
 | |
|             self._append("true")
 | |
|         elif obj is False:
 | |
|             self._append("false")
 | |
|         elif obj is None:
 | |
|             self._append("null")
 | |
|         else:
 | |
|             raise WriteException, "Cannot write in JSON: %s" % repr(obj)
 | |
| 
 | |
| def write(obj, escaped_forward_slash=False):
 | |
|     return JsonWriter().write(obj, escaped_forward_slash)
 | |
| 
 | |
| def read(s):
 | |
|     return JsonReader().read(s)
 |