1*77c1e3ccSAndroid Build Coastguard Worker#!/usr/bin/python 2*77c1e3ccSAndroid Build Coastguard Worker# 3*77c1e3ccSAndroid Build Coastguard Worker# Copyright (c) 2016, Alliance for Open Media. All rights reserved. 4*77c1e3ccSAndroid Build Coastguard Worker# 5*77c1e3ccSAndroid Build Coastguard Worker# This source code is subject to the terms of the BSD 2 Clause License and 6*77c1e3ccSAndroid Build Coastguard Worker# the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 7*77c1e3ccSAndroid Build Coastguard Worker# was not distributed with this source code in the LICENSE file, you can 8*77c1e3ccSAndroid Build Coastguard Worker# obtain it at www.aomedia.org/license/software. If the Alliance for Open 9*77c1e3ccSAndroid Build Coastguard Worker# Media Patent License 1.0 was not distributed with this source code in the 10*77c1e3ccSAndroid Build Coastguard Worker# PATENTS file, you can obtain it at www.aomedia.org/license/patent. 11*77c1e3ccSAndroid Build Coastguard Worker# 12*77c1e3ccSAndroid Build Coastguard Worker 13*77c1e3ccSAndroid Build Coastguard Worker"""Converts Python data into data for Google Visualization API clients. 14*77c1e3ccSAndroid Build Coastguard Worker 15*77c1e3ccSAndroid Build Coastguard WorkerThis library can be used to create a google.visualization.DataTable usable by 16*77c1e3ccSAndroid Build Coastguard Workervisualizations built on the Google Visualization API. Output formats are raw 17*77c1e3ccSAndroid Build Coastguard WorkerJSON, JSON response, JavaScript, CSV, and HTML table. 18*77c1e3ccSAndroid Build Coastguard Worker 19*77c1e3ccSAndroid Build Coastguard WorkerSee http://code.google.com/apis/visualization/ for documentation on the 20*77c1e3ccSAndroid Build Coastguard WorkerGoogle Visualization API. 21*77c1e3ccSAndroid Build Coastguard Worker""" 22*77c1e3ccSAndroid Build Coastguard Worker 23*77c1e3ccSAndroid Build Coastguard Worker__author__ = "Amit Weinstein, Misha Seltzer, Jacob Baskin" 24*77c1e3ccSAndroid Build Coastguard Worker 25*77c1e3ccSAndroid Build Coastguard Workerimport cgi 26*77c1e3ccSAndroid Build Coastguard Workerimport cStringIO 27*77c1e3ccSAndroid Build Coastguard Workerimport csv 28*77c1e3ccSAndroid Build Coastguard Workerimport datetime 29*77c1e3ccSAndroid Build Coastguard Workertry: 30*77c1e3ccSAndroid Build Coastguard Worker import json 31*77c1e3ccSAndroid Build Coastguard Workerexcept ImportError: 32*77c1e3ccSAndroid Build Coastguard Worker import simplejson as json 33*77c1e3ccSAndroid Build Coastguard Workerimport types 34*77c1e3ccSAndroid Build Coastguard Worker 35*77c1e3ccSAndroid Build Coastguard Worker 36*77c1e3ccSAndroid Build Coastguard Workerclass DataTableException(Exception): 37*77c1e3ccSAndroid Build Coastguard Worker """The general exception object thrown by DataTable.""" 38*77c1e3ccSAndroid Build Coastguard Worker pass 39*77c1e3ccSAndroid Build Coastguard Worker 40*77c1e3ccSAndroid Build Coastguard Worker 41*77c1e3ccSAndroid Build Coastguard Workerclass DataTableJSONEncoder(json.JSONEncoder): 42*77c1e3ccSAndroid Build Coastguard Worker """JSON encoder that handles date/time/datetime objects correctly.""" 43*77c1e3ccSAndroid Build Coastguard Worker 44*77c1e3ccSAndroid Build Coastguard Worker def __init__(self): 45*77c1e3ccSAndroid Build Coastguard Worker json.JSONEncoder.__init__(self, 46*77c1e3ccSAndroid Build Coastguard Worker separators=(",", ":"), 47*77c1e3ccSAndroid Build Coastguard Worker ensure_ascii=False) 48*77c1e3ccSAndroid Build Coastguard Worker 49*77c1e3ccSAndroid Build Coastguard Worker def default(self, o): 50*77c1e3ccSAndroid Build Coastguard Worker if isinstance(o, datetime.datetime): 51*77c1e3ccSAndroid Build Coastguard Worker if o.microsecond == 0: 52*77c1e3ccSAndroid Build Coastguard Worker # If the time doesn't have ms-resolution, leave it out to keep 53*77c1e3ccSAndroid Build Coastguard Worker # things smaller. 54*77c1e3ccSAndroid Build Coastguard Worker return "Date(%d,%d,%d,%d,%d,%d)" % ( 55*77c1e3ccSAndroid Build Coastguard Worker o.year, o.month - 1, o.day, o.hour, o.minute, o.second) 56*77c1e3ccSAndroid Build Coastguard Worker else: 57*77c1e3ccSAndroid Build Coastguard Worker return "Date(%d,%d,%d,%d,%d,%d,%d)" % ( 58*77c1e3ccSAndroid Build Coastguard Worker o.year, o.month - 1, o.day, o.hour, o.minute, o.second, 59*77c1e3ccSAndroid Build Coastguard Worker o.microsecond / 1000) 60*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(o, datetime.date): 61*77c1e3ccSAndroid Build Coastguard Worker return "Date(%d,%d,%d)" % (o.year, o.month - 1, o.day) 62*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(o, datetime.time): 63*77c1e3ccSAndroid Build Coastguard Worker return [o.hour, o.minute, o.second] 64*77c1e3ccSAndroid Build Coastguard Worker else: 65*77c1e3ccSAndroid Build Coastguard Worker return super(DataTableJSONEncoder, self).default(o) 66*77c1e3ccSAndroid Build Coastguard Worker 67*77c1e3ccSAndroid Build Coastguard Worker 68*77c1e3ccSAndroid Build Coastguard Workerclass DataTable(object): 69*77c1e3ccSAndroid Build Coastguard Worker """Wraps the data to convert to a Google Visualization API DataTable. 70*77c1e3ccSAndroid Build Coastguard Worker 71*77c1e3ccSAndroid Build Coastguard Worker Create this object, populate it with data, then call one of the ToJS... 72*77c1e3ccSAndroid Build Coastguard Worker methods to return a string representation of the data in the format described. 73*77c1e3ccSAndroid Build Coastguard Worker 74*77c1e3ccSAndroid Build Coastguard Worker You can clear all data from the object to reuse it, but you cannot clear 75*77c1e3ccSAndroid Build Coastguard Worker individual cells, rows, or columns. You also cannot modify the table schema 76*77c1e3ccSAndroid Build Coastguard Worker specified in the class constructor. 77*77c1e3ccSAndroid Build Coastguard Worker 78*77c1e3ccSAndroid Build Coastguard Worker You can add new data one or more rows at a time. All data added to an 79*77c1e3ccSAndroid Build Coastguard Worker instantiated DataTable must conform to the schema passed in to __init__(). 80*77c1e3ccSAndroid Build Coastguard Worker 81*77c1e3ccSAndroid Build Coastguard Worker You can reorder the columns in the output table, and also specify row sorting 82*77c1e3ccSAndroid Build Coastguard Worker order by column. The default column order is according to the original 83*77c1e3ccSAndroid Build Coastguard Worker table_description parameter. Default row sort order is ascending, by column 84*77c1e3ccSAndroid Build Coastguard Worker 1 values. For a dictionary, we sort the keys for order. 85*77c1e3ccSAndroid Build Coastguard Worker 86*77c1e3ccSAndroid Build Coastguard Worker The data and the table_description are closely tied, as described here: 87*77c1e3ccSAndroid Build Coastguard Worker 88*77c1e3ccSAndroid Build Coastguard Worker The table schema is defined in the class constructor's table_description 89*77c1e3ccSAndroid Build Coastguard Worker parameter. The user defines each column using a tuple of 90*77c1e3ccSAndroid Build Coastguard Worker (id[, type[, label[, custom_properties]]]). The default value for type is 91*77c1e3ccSAndroid Build Coastguard Worker string, label is the same as ID if not specified, and custom properties is 92*77c1e3ccSAndroid Build Coastguard Worker an empty dictionary if not specified. 93*77c1e3ccSAndroid Build Coastguard Worker 94*77c1e3ccSAndroid Build Coastguard Worker table_description is a dictionary or list, containing one or more column 95*77c1e3ccSAndroid Build Coastguard Worker descriptor tuples, nested dictionaries, and lists. Each dictionary key, list 96*77c1e3ccSAndroid Build Coastguard Worker element, or dictionary element must eventually be defined as 97*77c1e3ccSAndroid Build Coastguard Worker a column description tuple. Here's an example of a dictionary where the key 98*77c1e3ccSAndroid Build Coastguard Worker is a tuple, and the value is a list of two tuples: 99*77c1e3ccSAndroid Build Coastguard Worker {('a', 'number'): [('b', 'number'), ('c', 'string')]} 100*77c1e3ccSAndroid Build Coastguard Worker 101*77c1e3ccSAndroid Build Coastguard Worker This flexibility in data entry enables you to build and manipulate your data 102*77c1e3ccSAndroid Build Coastguard Worker in a Python structure that makes sense for your program. 103*77c1e3ccSAndroid Build Coastguard Worker 104*77c1e3ccSAndroid Build Coastguard Worker Add data to the table using the same nested design as the table's 105*77c1e3ccSAndroid Build Coastguard Worker table_description, replacing column descriptor tuples with cell data, and 106*77c1e3ccSAndroid Build Coastguard Worker each row is an element in the top level collection. This will be a bit 107*77c1e3ccSAndroid Build Coastguard Worker clearer after you look at the following examples showing the 108*77c1e3ccSAndroid Build Coastguard Worker table_description, matching data, and the resulting table: 109*77c1e3ccSAndroid Build Coastguard Worker 110*77c1e3ccSAndroid Build Coastguard Worker Columns as list of tuples [col1, col2, col3] 111*77c1e3ccSAndroid Build Coastguard Worker table_description: [('a', 'number'), ('b', 'string')] 112*77c1e3ccSAndroid Build Coastguard Worker AppendData( [[1, 'z'], [2, 'w'], [4, 'o'], [5, 'k']] ) 113*77c1e3ccSAndroid Build Coastguard Worker Table: 114*77c1e3ccSAndroid Build Coastguard Worker a b <--- these are column ids/labels 115*77c1e3ccSAndroid Build Coastguard Worker 1 z 116*77c1e3ccSAndroid Build Coastguard Worker 2 w 117*77c1e3ccSAndroid Build Coastguard Worker 4 o 118*77c1e3ccSAndroid Build Coastguard Worker 5 k 119*77c1e3ccSAndroid Build Coastguard Worker 120*77c1e3ccSAndroid Build Coastguard Worker Dictionary of columns, where key is a column, and value is a list of 121*77c1e3ccSAndroid Build Coastguard Worker columns {col1: [col2, col3]} 122*77c1e3ccSAndroid Build Coastguard Worker table_description: {('a', 'number'): [('b', 'number'), ('c', 'string')]} 123*77c1e3ccSAndroid Build Coastguard Worker AppendData( data: {1: [2, 'z'], 3: [4, 'w']} 124*77c1e3ccSAndroid Build Coastguard Worker Table: 125*77c1e3ccSAndroid Build Coastguard Worker a b c 126*77c1e3ccSAndroid Build Coastguard Worker 1 2 z 127*77c1e3ccSAndroid Build Coastguard Worker 3 4 w 128*77c1e3ccSAndroid Build Coastguard Worker 129*77c1e3ccSAndroid Build Coastguard Worker Dictionary where key is a column, and the value is itself a dictionary of 130*77c1e3ccSAndroid Build Coastguard Worker columns {col1: {col2, col3}} 131*77c1e3ccSAndroid Build Coastguard Worker table_description: {('a', 'number'): {'b': 'number', 'c': 'string'}} 132*77c1e3ccSAndroid Build Coastguard Worker AppendData( data: {1: {'b': 2, 'c': 'z'}, 3: {'b': 4, 'c': 'w'}} 133*77c1e3ccSAndroid Build Coastguard Worker Table: 134*77c1e3ccSAndroid Build Coastguard Worker a b c 135*77c1e3ccSAndroid Build Coastguard Worker 1 2 z 136*77c1e3ccSAndroid Build Coastguard Worker 3 4 w 137*77c1e3ccSAndroid Build Coastguard Worker """ 138*77c1e3ccSAndroid Build Coastguard Worker 139*77c1e3ccSAndroid Build Coastguard Worker def __init__(self, table_description, data=None, custom_properties=None): 140*77c1e3ccSAndroid Build Coastguard Worker """Initialize the data table from a table schema and (optionally) data. 141*77c1e3ccSAndroid Build Coastguard Worker 142*77c1e3ccSAndroid Build Coastguard Worker See the class documentation for more information on table schema and data 143*77c1e3ccSAndroid Build Coastguard Worker values. 144*77c1e3ccSAndroid Build Coastguard Worker 145*77c1e3ccSAndroid Build Coastguard Worker Args: 146*77c1e3ccSAndroid Build Coastguard Worker table_description: A table schema, following one of the formats described 147*77c1e3ccSAndroid Build Coastguard Worker in TableDescriptionParser(). Schemas describe the 148*77c1e3ccSAndroid Build Coastguard Worker column names, data types, and labels. See 149*77c1e3ccSAndroid Build Coastguard Worker TableDescriptionParser() for acceptable formats. 150*77c1e3ccSAndroid Build Coastguard Worker data: Optional. If given, fills the table with the given data. The data 151*77c1e3ccSAndroid Build Coastguard Worker structure must be consistent with schema in table_description. See 152*77c1e3ccSAndroid Build Coastguard Worker the class documentation for more information on acceptable data. You 153*77c1e3ccSAndroid Build Coastguard Worker can add data later by calling AppendData(). 154*77c1e3ccSAndroid Build Coastguard Worker custom_properties: Optional. A dictionary from string to string that 155*77c1e3ccSAndroid Build Coastguard Worker goes into the table's custom properties. This can be 156*77c1e3ccSAndroid Build Coastguard Worker later changed by changing self.custom_properties. 157*77c1e3ccSAndroid Build Coastguard Worker 158*77c1e3ccSAndroid Build Coastguard Worker Raises: 159*77c1e3ccSAndroid Build Coastguard Worker DataTableException: Raised if the data and the description did not match, 160*77c1e3ccSAndroid Build Coastguard Worker or did not use the supported formats. 161*77c1e3ccSAndroid Build Coastguard Worker """ 162*77c1e3ccSAndroid Build Coastguard Worker self.__columns = self.TableDescriptionParser(table_description) 163*77c1e3ccSAndroid Build Coastguard Worker self.__data = [] 164*77c1e3ccSAndroid Build Coastguard Worker self.custom_properties = {} 165*77c1e3ccSAndroid Build Coastguard Worker if custom_properties is not None: 166*77c1e3ccSAndroid Build Coastguard Worker self.custom_properties = custom_properties 167*77c1e3ccSAndroid Build Coastguard Worker if data: 168*77c1e3ccSAndroid Build Coastguard Worker self.LoadData(data) 169*77c1e3ccSAndroid Build Coastguard Worker 170*77c1e3ccSAndroid Build Coastguard Worker @staticmethod 171*77c1e3ccSAndroid Build Coastguard Worker def CoerceValue(value, value_type): 172*77c1e3ccSAndroid Build Coastguard Worker """Coerces a single value into the type expected for its column. 173*77c1e3ccSAndroid Build Coastguard Worker 174*77c1e3ccSAndroid Build Coastguard Worker Internal helper method. 175*77c1e3ccSAndroid Build Coastguard Worker 176*77c1e3ccSAndroid Build Coastguard Worker Args: 177*77c1e3ccSAndroid Build Coastguard Worker value: The value which should be converted 178*77c1e3ccSAndroid Build Coastguard Worker value_type: One of "string", "number", "boolean", "date", "datetime" or 179*77c1e3ccSAndroid Build Coastguard Worker "timeofday". 180*77c1e3ccSAndroid Build Coastguard Worker 181*77c1e3ccSAndroid Build Coastguard Worker Returns: 182*77c1e3ccSAndroid Build Coastguard Worker An item of the Python type appropriate to the given value_type. Strings 183*77c1e3ccSAndroid Build Coastguard Worker are also converted to Unicode using UTF-8 encoding if necessary. 184*77c1e3ccSAndroid Build Coastguard Worker If a tuple is given, it should be in one of the following forms: 185*77c1e3ccSAndroid Build Coastguard Worker - (value, formatted value) 186*77c1e3ccSAndroid Build Coastguard Worker - (value, formatted value, custom properties) 187*77c1e3ccSAndroid Build Coastguard Worker where the formatted value is a string, and custom properties is a 188*77c1e3ccSAndroid Build Coastguard Worker dictionary of the custom properties for this cell. 189*77c1e3ccSAndroid Build Coastguard Worker To specify custom properties without specifying formatted value, one can 190*77c1e3ccSAndroid Build Coastguard Worker pass None as the formatted value. 191*77c1e3ccSAndroid Build Coastguard Worker One can also have a null-valued cell with formatted value and/or custom 192*77c1e3ccSAndroid Build Coastguard Worker properties by specifying None for the value. 193*77c1e3ccSAndroid Build Coastguard Worker This method ignores the custom properties except for checking that it is a 194*77c1e3ccSAndroid Build Coastguard Worker dictionary. The custom properties are handled in the ToJSon and ToJSCode 195*77c1e3ccSAndroid Build Coastguard Worker methods. 196*77c1e3ccSAndroid Build Coastguard Worker The real type of the given value is not strictly checked. For example, 197*77c1e3ccSAndroid Build Coastguard Worker any type can be used for string - as we simply take its str( ) and for 198*77c1e3ccSAndroid Build Coastguard Worker boolean value we just check "if value". 199*77c1e3ccSAndroid Build Coastguard Worker Examples: 200*77c1e3ccSAndroid Build Coastguard Worker CoerceValue(None, "string") returns None 201*77c1e3ccSAndroid Build Coastguard Worker CoerceValue((5, "5$"), "number") returns (5, "5$") 202*77c1e3ccSAndroid Build Coastguard Worker CoerceValue(100, "string") returns "100" 203*77c1e3ccSAndroid Build Coastguard Worker CoerceValue(0, "boolean") returns False 204*77c1e3ccSAndroid Build Coastguard Worker 205*77c1e3ccSAndroid Build Coastguard Worker Raises: 206*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The value and type did not match in a not-recoverable 207*77c1e3ccSAndroid Build Coastguard Worker way, for example given value 'abc' for type 'number'. 208*77c1e3ccSAndroid Build Coastguard Worker """ 209*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, tuple): 210*77c1e3ccSAndroid Build Coastguard Worker # In case of a tuple, we run the same function on the value itself and 211*77c1e3ccSAndroid Build Coastguard Worker # add the formatted value. 212*77c1e3ccSAndroid Build Coastguard Worker if (len(value) not in [2, 3] or 213*77c1e3ccSAndroid Build Coastguard Worker (len(value) == 3 and not isinstance(value[2], dict))): 214*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Wrong format for value and formatting - %s." % 215*77c1e3ccSAndroid Build Coastguard Worker str(value)) 216*77c1e3ccSAndroid Build Coastguard Worker if not isinstance(value[1], types.StringTypes + (types.NoneType,)): 217*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Formatted value is not string, given %s." % 218*77c1e3ccSAndroid Build Coastguard Worker type(value[1])) 219*77c1e3ccSAndroid Build Coastguard Worker js_value = DataTable.CoerceValue(value[0], value_type) 220*77c1e3ccSAndroid Build Coastguard Worker return (js_value,) + value[1:] 221*77c1e3ccSAndroid Build Coastguard Worker 222*77c1e3ccSAndroid Build Coastguard Worker t_value = type(value) 223*77c1e3ccSAndroid Build Coastguard Worker if value is None: 224*77c1e3ccSAndroid Build Coastguard Worker return value 225*77c1e3ccSAndroid Build Coastguard Worker if value_type == "boolean": 226*77c1e3ccSAndroid Build Coastguard Worker return bool(value) 227*77c1e3ccSAndroid Build Coastguard Worker 228*77c1e3ccSAndroid Build Coastguard Worker elif value_type == "number": 229*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, (int, long, float)): 230*77c1e3ccSAndroid Build Coastguard Worker return value 231*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Wrong type %s when expected number" % t_value) 232*77c1e3ccSAndroid Build Coastguard Worker 233*77c1e3ccSAndroid Build Coastguard Worker elif value_type == "string": 234*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, unicode): 235*77c1e3ccSAndroid Build Coastguard Worker return value 236*77c1e3ccSAndroid Build Coastguard Worker else: 237*77c1e3ccSAndroid Build Coastguard Worker return str(value).decode("utf-8") 238*77c1e3ccSAndroid Build Coastguard Worker 239*77c1e3ccSAndroid Build Coastguard Worker elif value_type == "date": 240*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, datetime.datetime): 241*77c1e3ccSAndroid Build Coastguard Worker return datetime.date(value.year, value.month, value.day) 242*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, datetime.date): 243*77c1e3ccSAndroid Build Coastguard Worker return value 244*77c1e3ccSAndroid Build Coastguard Worker else: 245*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Wrong type %s when expected date" % t_value) 246*77c1e3ccSAndroid Build Coastguard Worker 247*77c1e3ccSAndroid Build Coastguard Worker elif value_type == "timeofday": 248*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, datetime.datetime): 249*77c1e3ccSAndroid Build Coastguard Worker return datetime.time(value.hour, value.minute, value.second) 250*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, datetime.time): 251*77c1e3ccSAndroid Build Coastguard Worker return value 252*77c1e3ccSAndroid Build Coastguard Worker else: 253*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Wrong type %s when expected time" % t_value) 254*77c1e3ccSAndroid Build Coastguard Worker 255*77c1e3ccSAndroid Build Coastguard Worker elif value_type == "datetime": 256*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, datetime.datetime): 257*77c1e3ccSAndroid Build Coastguard Worker return value 258*77c1e3ccSAndroid Build Coastguard Worker else: 259*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Wrong type %s when expected datetime" % 260*77c1e3ccSAndroid Build Coastguard Worker t_value) 261*77c1e3ccSAndroid Build Coastguard Worker # If we got here, it means the given value_type was not one of the 262*77c1e3ccSAndroid Build Coastguard Worker # supported types. 263*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Unsupported type %s" % value_type) 264*77c1e3ccSAndroid Build Coastguard Worker 265*77c1e3ccSAndroid Build Coastguard Worker @staticmethod 266*77c1e3ccSAndroid Build Coastguard Worker def EscapeForJSCode(encoder, value): 267*77c1e3ccSAndroid Build Coastguard Worker if value is None: 268*77c1e3ccSAndroid Build Coastguard Worker return "null" 269*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, datetime.datetime): 270*77c1e3ccSAndroid Build Coastguard Worker if value.microsecond == 0: 271*77c1e3ccSAndroid Build Coastguard Worker # If it's not ms-resolution, leave that out to save space. 272*77c1e3ccSAndroid Build Coastguard Worker return "new Date(%d,%d,%d,%d,%d,%d)" % (value.year, 273*77c1e3ccSAndroid Build Coastguard Worker value.month - 1, # To match JS 274*77c1e3ccSAndroid Build Coastguard Worker value.day, 275*77c1e3ccSAndroid Build Coastguard Worker value.hour, 276*77c1e3ccSAndroid Build Coastguard Worker value.minute, 277*77c1e3ccSAndroid Build Coastguard Worker value.second) 278*77c1e3ccSAndroid Build Coastguard Worker else: 279*77c1e3ccSAndroid Build Coastguard Worker return "new Date(%d,%d,%d,%d,%d,%d,%d)" % (value.year, 280*77c1e3ccSAndroid Build Coastguard Worker value.month - 1, # match JS 281*77c1e3ccSAndroid Build Coastguard Worker value.day, 282*77c1e3ccSAndroid Build Coastguard Worker value.hour, 283*77c1e3ccSAndroid Build Coastguard Worker value.minute, 284*77c1e3ccSAndroid Build Coastguard Worker value.second, 285*77c1e3ccSAndroid Build Coastguard Worker value.microsecond / 1000) 286*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, datetime.date): 287*77c1e3ccSAndroid Build Coastguard Worker return "new Date(%d,%d,%d)" % (value.year, value.month - 1, value.day) 288*77c1e3ccSAndroid Build Coastguard Worker else: 289*77c1e3ccSAndroid Build Coastguard Worker return encoder.encode(value) 290*77c1e3ccSAndroid Build Coastguard Worker 291*77c1e3ccSAndroid Build Coastguard Worker @staticmethod 292*77c1e3ccSAndroid Build Coastguard Worker def ToString(value): 293*77c1e3ccSAndroid Build Coastguard Worker if value is None: 294*77c1e3ccSAndroid Build Coastguard Worker return "(empty)" 295*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, (datetime.datetime, 296*77c1e3ccSAndroid Build Coastguard Worker datetime.date, 297*77c1e3ccSAndroid Build Coastguard Worker datetime.time)): 298*77c1e3ccSAndroid Build Coastguard Worker return str(value) 299*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, unicode): 300*77c1e3ccSAndroid Build Coastguard Worker return value 301*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, bool): 302*77c1e3ccSAndroid Build Coastguard Worker return str(value).lower() 303*77c1e3ccSAndroid Build Coastguard Worker else: 304*77c1e3ccSAndroid Build Coastguard Worker return str(value).decode("utf-8") 305*77c1e3ccSAndroid Build Coastguard Worker 306*77c1e3ccSAndroid Build Coastguard Worker @staticmethod 307*77c1e3ccSAndroid Build Coastguard Worker def ColumnTypeParser(description): 308*77c1e3ccSAndroid Build Coastguard Worker """Parses a single column description. Internal helper method. 309*77c1e3ccSAndroid Build Coastguard Worker 310*77c1e3ccSAndroid Build Coastguard Worker Args: 311*77c1e3ccSAndroid Build Coastguard Worker description: a column description in the possible formats: 312*77c1e3ccSAndroid Build Coastguard Worker 'id' 313*77c1e3ccSAndroid Build Coastguard Worker ('id',) 314*77c1e3ccSAndroid Build Coastguard Worker ('id', 'type') 315*77c1e3ccSAndroid Build Coastguard Worker ('id', 'type', 'label') 316*77c1e3ccSAndroid Build Coastguard Worker ('id', 'type', 'label', {'custom_prop1': 'custom_val1'}) 317*77c1e3ccSAndroid Build Coastguard Worker Returns: 318*77c1e3ccSAndroid Build Coastguard Worker Dictionary with the following keys: id, label, type, and 319*77c1e3ccSAndroid Build Coastguard Worker custom_properties where: 320*77c1e3ccSAndroid Build Coastguard Worker - If label not given, it equals the id. 321*77c1e3ccSAndroid Build Coastguard Worker - If type not given, string is used by default. 322*77c1e3ccSAndroid Build Coastguard Worker - If custom properties are not given, an empty dictionary is used by 323*77c1e3ccSAndroid Build Coastguard Worker default. 324*77c1e3ccSAndroid Build Coastguard Worker 325*77c1e3ccSAndroid Build Coastguard Worker Raises: 326*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The column description did not match the RE, or 327*77c1e3ccSAndroid Build Coastguard Worker unsupported type was passed. 328*77c1e3ccSAndroid Build Coastguard Worker """ 329*77c1e3ccSAndroid Build Coastguard Worker if not description: 330*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Description error: empty description given") 331*77c1e3ccSAndroid Build Coastguard Worker 332*77c1e3ccSAndroid Build Coastguard Worker if not isinstance(description, (types.StringTypes, tuple)): 333*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Description error: expected either string or " 334*77c1e3ccSAndroid Build Coastguard Worker "tuple, got %s." % type(description)) 335*77c1e3ccSAndroid Build Coastguard Worker 336*77c1e3ccSAndroid Build Coastguard Worker if isinstance(description, types.StringTypes): 337*77c1e3ccSAndroid Build Coastguard Worker description = (description,) 338*77c1e3ccSAndroid Build Coastguard Worker 339*77c1e3ccSAndroid Build Coastguard Worker # According to the tuple's length, we fill the keys 340*77c1e3ccSAndroid Build Coastguard Worker # We verify everything is of type string 341*77c1e3ccSAndroid Build Coastguard Worker for elem in description[:3]: 342*77c1e3ccSAndroid Build Coastguard Worker if not isinstance(elem, types.StringTypes): 343*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Description error: expected tuple of " 344*77c1e3ccSAndroid Build Coastguard Worker "strings, current element of type %s." % 345*77c1e3ccSAndroid Build Coastguard Worker type(elem)) 346*77c1e3ccSAndroid Build Coastguard Worker desc_dict = {"id": description[0], 347*77c1e3ccSAndroid Build Coastguard Worker "label": description[0], 348*77c1e3ccSAndroid Build Coastguard Worker "type": "string", 349*77c1e3ccSAndroid Build Coastguard Worker "custom_properties": {}} 350*77c1e3ccSAndroid Build Coastguard Worker if len(description) > 1: 351*77c1e3ccSAndroid Build Coastguard Worker desc_dict["type"] = description[1].lower() 352*77c1e3ccSAndroid Build Coastguard Worker if len(description) > 2: 353*77c1e3ccSAndroid Build Coastguard Worker desc_dict["label"] = description[2] 354*77c1e3ccSAndroid Build Coastguard Worker if len(description) > 3: 355*77c1e3ccSAndroid Build Coastguard Worker if not isinstance(description[3], dict): 356*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Description error: expected custom " 357*77c1e3ccSAndroid Build Coastguard Worker "properties of type dict, current element " 358*77c1e3ccSAndroid Build Coastguard Worker "of type %s." % type(description[3])) 359*77c1e3ccSAndroid Build Coastguard Worker desc_dict["custom_properties"] = description[3] 360*77c1e3ccSAndroid Build Coastguard Worker if len(description) > 4: 361*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Description error: tuple of length > 4") 362*77c1e3ccSAndroid Build Coastguard Worker if desc_dict["type"] not in ["string", "number", "boolean", 363*77c1e3ccSAndroid Build Coastguard Worker "date", "datetime", "timeofday"]: 364*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException( 365*77c1e3ccSAndroid Build Coastguard Worker "Description error: unsupported type '%s'" % desc_dict["type"]) 366*77c1e3ccSAndroid Build Coastguard Worker return desc_dict 367*77c1e3ccSAndroid Build Coastguard Worker 368*77c1e3ccSAndroid Build Coastguard Worker @staticmethod 369*77c1e3ccSAndroid Build Coastguard Worker def TableDescriptionParser(table_description, depth=0): 370*77c1e3ccSAndroid Build Coastguard Worker """Parses the table_description object for internal use. 371*77c1e3ccSAndroid Build Coastguard Worker 372*77c1e3ccSAndroid Build Coastguard Worker Parses the user-submitted table description into an internal format used 373*77c1e3ccSAndroid Build Coastguard Worker by the Python DataTable class. Returns the flat list of parsed columns. 374*77c1e3ccSAndroid Build Coastguard Worker 375*77c1e3ccSAndroid Build Coastguard Worker Args: 376*77c1e3ccSAndroid Build Coastguard Worker table_description: A description of the table which should comply 377*77c1e3ccSAndroid Build Coastguard Worker with one of the formats described below. 378*77c1e3ccSAndroid Build Coastguard Worker depth: Optional. The depth of the first level in the current description. 379*77c1e3ccSAndroid Build Coastguard Worker Used by recursive calls to this function. 380*77c1e3ccSAndroid Build Coastguard Worker 381*77c1e3ccSAndroid Build Coastguard Worker Returns: 382*77c1e3ccSAndroid Build Coastguard Worker List of columns, where each column represented by a dictionary with the 383*77c1e3ccSAndroid Build Coastguard Worker keys: id, label, type, depth, container which means the following: 384*77c1e3ccSAndroid Build Coastguard Worker - id: the id of the column 385*77c1e3ccSAndroid Build Coastguard Worker - name: The name of the column 386*77c1e3ccSAndroid Build Coastguard Worker - type: The datatype of the elements in this column. Allowed types are 387*77c1e3ccSAndroid Build Coastguard Worker described in ColumnTypeParser(). 388*77c1e3ccSAndroid Build Coastguard Worker - depth: The depth of this column in the table description 389*77c1e3ccSAndroid Build Coastguard Worker - container: 'dict', 'iter' or 'scalar' for parsing the format easily. 390*77c1e3ccSAndroid Build Coastguard Worker - custom_properties: The custom properties for this column. 391*77c1e3ccSAndroid Build Coastguard Worker The returned description is flattened regardless of how it was given. 392*77c1e3ccSAndroid Build Coastguard Worker 393*77c1e3ccSAndroid Build Coastguard Worker Raises: 394*77c1e3ccSAndroid Build Coastguard Worker DataTableException: Error in a column description or in the description 395*77c1e3ccSAndroid Build Coastguard Worker structure. 396*77c1e3ccSAndroid Build Coastguard Worker 397*77c1e3ccSAndroid Build Coastguard Worker Examples: 398*77c1e3ccSAndroid Build Coastguard Worker A column description can be of the following forms: 399*77c1e3ccSAndroid Build Coastguard Worker 'id' 400*77c1e3ccSAndroid Build Coastguard Worker ('id',) 401*77c1e3ccSAndroid Build Coastguard Worker ('id', 'type') 402*77c1e3ccSAndroid Build Coastguard Worker ('id', 'type', 'label') 403*77c1e3ccSAndroid Build Coastguard Worker ('id', 'type', 'label', {'custom_prop1': 'custom_val1'}) 404*77c1e3ccSAndroid Build Coastguard Worker or as a dictionary: 405*77c1e3ccSAndroid Build Coastguard Worker 'id': 'type' 406*77c1e3ccSAndroid Build Coastguard Worker 'id': ('type',) 407*77c1e3ccSAndroid Build Coastguard Worker 'id': ('type', 'label') 408*77c1e3ccSAndroid Build Coastguard Worker 'id': ('type', 'label', {'custom_prop1': 'custom_val1'}) 409*77c1e3ccSAndroid Build Coastguard Worker If the type is not specified, we treat it as string. 410*77c1e3ccSAndroid Build Coastguard Worker If no specific label is given, the label is simply the id. 411*77c1e3ccSAndroid Build Coastguard Worker If no custom properties are given, we use an empty dictionary. 412*77c1e3ccSAndroid Build Coastguard Worker 413*77c1e3ccSAndroid Build Coastguard Worker input: [('a', 'date'), ('b', 'timeofday', 'b', {'foo': 'bar'})] 414*77c1e3ccSAndroid Build Coastguard Worker output: [{'id': 'a', 'label': 'a', 'type': 'date', 415*77c1e3ccSAndroid Build Coastguard Worker 'depth': 0, 'container': 'iter', 'custom_properties': {}}, 416*77c1e3ccSAndroid Build Coastguard Worker {'id': 'b', 'label': 'b', 'type': 'timeofday', 417*77c1e3ccSAndroid Build Coastguard Worker 'depth': 0, 'container': 'iter', 418*77c1e3ccSAndroid Build Coastguard Worker 'custom_properties': {'foo': 'bar'}}] 419*77c1e3ccSAndroid Build Coastguard Worker 420*77c1e3ccSAndroid Build Coastguard Worker input: {'a': [('b', 'number'), ('c', 'string', 'column c')]} 421*77c1e3ccSAndroid Build Coastguard Worker output: [{'id': 'a', 'label': 'a', 'type': 'string', 422*77c1e3ccSAndroid Build Coastguard Worker 'depth': 0, 'container': 'dict', 'custom_properties': {}}, 423*77c1e3ccSAndroid Build Coastguard Worker {'id': 'b', 'label': 'b', 'type': 'number', 424*77c1e3ccSAndroid Build Coastguard Worker 'depth': 1, 'container': 'iter', 'custom_properties': {}}, 425*77c1e3ccSAndroid Build Coastguard Worker {'id': 'c', 'label': 'column c', 'type': 'string', 426*77c1e3ccSAndroid Build Coastguard Worker 'depth': 1, 'container': 'iter', 'custom_properties': {}}] 427*77c1e3ccSAndroid Build Coastguard Worker 428*77c1e3ccSAndroid Build Coastguard Worker input: {('a', 'number', 'column a'): { 'b': 'number', 'c': 'string'}} 429*77c1e3ccSAndroid Build Coastguard Worker output: [{'id': 'a', 'label': 'column a', 'type': 'number', 430*77c1e3ccSAndroid Build Coastguard Worker 'depth': 0, 'container': 'dict', 'custom_properties': {}}, 431*77c1e3ccSAndroid Build Coastguard Worker {'id': 'b', 'label': 'b', 'type': 'number', 432*77c1e3ccSAndroid Build Coastguard Worker 'depth': 1, 'container': 'dict', 'custom_properties': {}}, 433*77c1e3ccSAndroid Build Coastguard Worker {'id': 'c', 'label': 'c', 'type': 'string', 434*77c1e3ccSAndroid Build Coastguard Worker 'depth': 1, 'container': 'dict', 'custom_properties': {}}] 435*77c1e3ccSAndroid Build Coastguard Worker 436*77c1e3ccSAndroid Build Coastguard Worker input: { ('w', 'string', 'word'): ('c', 'number', 'count') } 437*77c1e3ccSAndroid Build Coastguard Worker output: [{'id': 'w', 'label': 'word', 'type': 'string', 438*77c1e3ccSAndroid Build Coastguard Worker 'depth': 0, 'container': 'dict', 'custom_properties': {}}, 439*77c1e3ccSAndroid Build Coastguard Worker {'id': 'c', 'label': 'count', 'type': 'number', 440*77c1e3ccSAndroid Build Coastguard Worker 'depth': 1, 'container': 'scalar', 'custom_properties': {}}] 441*77c1e3ccSAndroid Build Coastguard Worker 442*77c1e3ccSAndroid Build Coastguard Worker input: {'a': ('number', 'column a'), 'b': ('string', 'column b')} 443*77c1e3ccSAndroid Build Coastguard Worker output: [{'id': 'a', 'label': 'column a', 'type': 'number', 'depth': 0, 444*77c1e3ccSAndroid Build Coastguard Worker 'container': 'dict', 'custom_properties': {}}, 445*77c1e3ccSAndroid Build Coastguard Worker {'id': 'b', 'label': 'column b', 'type': 'string', 'depth': 0, 446*77c1e3ccSAndroid Build Coastguard Worker 'container': 'dict', 'custom_properties': {}} 447*77c1e3ccSAndroid Build Coastguard Worker 448*77c1e3ccSAndroid Build Coastguard Worker NOTE: there might be ambiguity in the case of a dictionary representation 449*77c1e3ccSAndroid Build Coastguard Worker of a single column. For example, the following description can be parsed 450*77c1e3ccSAndroid Build Coastguard Worker in 2 different ways: {'a': ('b', 'c')} can be thought of a single column 451*77c1e3ccSAndroid Build Coastguard Worker with the id 'a', of type 'b' and the label 'c', or as 2 columns: one named 452*77c1e3ccSAndroid Build Coastguard Worker 'a', and the other named 'b' of type 'c'. We choose the first option by 453*77c1e3ccSAndroid Build Coastguard Worker default, and in case the second option is the right one, it is possible to 454*77c1e3ccSAndroid Build Coastguard Worker make the key into a tuple (i.e. {('a',): ('b', 'c')}) or add more info 455*77c1e3ccSAndroid Build Coastguard Worker into the tuple, thus making it look like this: {'a': ('b', 'c', 'b', {})} 456*77c1e3ccSAndroid Build Coastguard Worker -- second 'b' is the label, and {} is the custom properties field. 457*77c1e3ccSAndroid Build Coastguard Worker """ 458*77c1e3ccSAndroid Build Coastguard Worker # For the recursion step, we check for a scalar object (string or tuple) 459*77c1e3ccSAndroid Build Coastguard Worker if isinstance(table_description, (types.StringTypes, tuple)): 460*77c1e3ccSAndroid Build Coastguard Worker parsed_col = DataTable.ColumnTypeParser(table_description) 461*77c1e3ccSAndroid Build Coastguard Worker parsed_col["depth"] = depth 462*77c1e3ccSAndroid Build Coastguard Worker parsed_col["container"] = "scalar" 463*77c1e3ccSAndroid Build Coastguard Worker return [parsed_col] 464*77c1e3ccSAndroid Build Coastguard Worker 465*77c1e3ccSAndroid Build Coastguard Worker # Since it is not scalar, table_description must be iterable. 466*77c1e3ccSAndroid Build Coastguard Worker if not hasattr(table_description, "__iter__"): 467*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Expected an iterable object, got %s" % 468*77c1e3ccSAndroid Build Coastguard Worker type(table_description)) 469*77c1e3ccSAndroid Build Coastguard Worker if not isinstance(table_description, dict): 470*77c1e3ccSAndroid Build Coastguard Worker # We expects a non-dictionary iterable item. 471*77c1e3ccSAndroid Build Coastguard Worker columns = [] 472*77c1e3ccSAndroid Build Coastguard Worker for desc in table_description: 473*77c1e3ccSAndroid Build Coastguard Worker parsed_col = DataTable.ColumnTypeParser(desc) 474*77c1e3ccSAndroid Build Coastguard Worker parsed_col["depth"] = depth 475*77c1e3ccSAndroid Build Coastguard Worker parsed_col["container"] = "iter" 476*77c1e3ccSAndroid Build Coastguard Worker columns.append(parsed_col) 477*77c1e3ccSAndroid Build Coastguard Worker if not columns: 478*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Description iterable objects should not" 479*77c1e3ccSAndroid Build Coastguard Worker " be empty.") 480*77c1e3ccSAndroid Build Coastguard Worker return columns 481*77c1e3ccSAndroid Build Coastguard Worker # The other case is a dictionary 482*77c1e3ccSAndroid Build Coastguard Worker if not table_description: 483*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Empty dictionaries are not allowed inside" 484*77c1e3ccSAndroid Build Coastguard Worker " description") 485*77c1e3ccSAndroid Build Coastguard Worker 486*77c1e3ccSAndroid Build Coastguard Worker # To differentiate between the two cases of more levels below or this is 487*77c1e3ccSAndroid Build Coastguard Worker # the most inner dictionary, we consider the number of keys (more then one 488*77c1e3ccSAndroid Build Coastguard Worker # key is indication for most inner dictionary) and the type of the key and 489*77c1e3ccSAndroid Build Coastguard Worker # value in case of only 1 key (if the type of key is string and the type of 490*77c1e3ccSAndroid Build Coastguard Worker # the value is a tuple of 0-3 items, we assume this is the most inner 491*77c1e3ccSAndroid Build Coastguard Worker # dictionary). 492*77c1e3ccSAndroid Build Coastguard Worker # NOTE: this way of differentiating might create ambiguity. See docs. 493*77c1e3ccSAndroid Build Coastguard Worker if (len(table_description) != 1 or 494*77c1e3ccSAndroid Build Coastguard Worker (isinstance(table_description.keys()[0], types.StringTypes) and 495*77c1e3ccSAndroid Build Coastguard Worker isinstance(table_description.values()[0], tuple) and 496*77c1e3ccSAndroid Build Coastguard Worker len(table_description.values()[0]) < 4)): 497*77c1e3ccSAndroid Build Coastguard Worker # This is the most inner dictionary. Parsing types. 498*77c1e3ccSAndroid Build Coastguard Worker columns = [] 499*77c1e3ccSAndroid Build Coastguard Worker # We sort the items, equivalent to sort the keys since they are unique 500*77c1e3ccSAndroid Build Coastguard Worker for key, value in sorted(table_description.items()): 501*77c1e3ccSAndroid Build Coastguard Worker # We parse the column type as (key, type) or (key, type, label) using 502*77c1e3ccSAndroid Build Coastguard Worker # ColumnTypeParser. 503*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, tuple): 504*77c1e3ccSAndroid Build Coastguard Worker parsed_col = DataTable.ColumnTypeParser((key,) + value) 505*77c1e3ccSAndroid Build Coastguard Worker else: 506*77c1e3ccSAndroid Build Coastguard Worker parsed_col = DataTable.ColumnTypeParser((key, value)) 507*77c1e3ccSAndroid Build Coastguard Worker parsed_col["depth"] = depth 508*77c1e3ccSAndroid Build Coastguard Worker parsed_col["container"] = "dict" 509*77c1e3ccSAndroid Build Coastguard Worker columns.append(parsed_col) 510*77c1e3ccSAndroid Build Coastguard Worker return columns 511*77c1e3ccSAndroid Build Coastguard Worker # This is an outer dictionary, must have at most one key. 512*77c1e3ccSAndroid Build Coastguard Worker parsed_col = DataTable.ColumnTypeParser(table_description.keys()[0]) 513*77c1e3ccSAndroid Build Coastguard Worker parsed_col["depth"] = depth 514*77c1e3ccSAndroid Build Coastguard Worker parsed_col["container"] = "dict" 515*77c1e3ccSAndroid Build Coastguard Worker return ([parsed_col] + 516*77c1e3ccSAndroid Build Coastguard Worker DataTable.TableDescriptionParser(table_description.values()[0], 517*77c1e3ccSAndroid Build Coastguard Worker depth=depth + 1)) 518*77c1e3ccSAndroid Build Coastguard Worker 519*77c1e3ccSAndroid Build Coastguard Worker @property 520*77c1e3ccSAndroid Build Coastguard Worker def columns(self): 521*77c1e3ccSAndroid Build Coastguard Worker """Returns the parsed table description.""" 522*77c1e3ccSAndroid Build Coastguard Worker return self.__columns 523*77c1e3ccSAndroid Build Coastguard Worker 524*77c1e3ccSAndroid Build Coastguard Worker def NumberOfRows(self): 525*77c1e3ccSAndroid Build Coastguard Worker """Returns the number of rows in the current data stored in the table.""" 526*77c1e3ccSAndroid Build Coastguard Worker return len(self.__data) 527*77c1e3ccSAndroid Build Coastguard Worker 528*77c1e3ccSAndroid Build Coastguard Worker def SetRowsCustomProperties(self, rows, custom_properties): 529*77c1e3ccSAndroid Build Coastguard Worker """Sets the custom properties for given row(s). 530*77c1e3ccSAndroid Build Coastguard Worker 531*77c1e3ccSAndroid Build Coastguard Worker Can accept a single row or an iterable of rows. 532*77c1e3ccSAndroid Build Coastguard Worker Sets the given custom properties for all specified rows. 533*77c1e3ccSAndroid Build Coastguard Worker 534*77c1e3ccSAndroid Build Coastguard Worker Args: 535*77c1e3ccSAndroid Build Coastguard Worker rows: The row, or rows, to set the custom properties for. 536*77c1e3ccSAndroid Build Coastguard Worker custom_properties: A string to string dictionary of custom properties to 537*77c1e3ccSAndroid Build Coastguard Worker set for all rows. 538*77c1e3ccSAndroid Build Coastguard Worker """ 539*77c1e3ccSAndroid Build Coastguard Worker if not hasattr(rows, "__iter__"): 540*77c1e3ccSAndroid Build Coastguard Worker rows = [rows] 541*77c1e3ccSAndroid Build Coastguard Worker for row in rows: 542*77c1e3ccSAndroid Build Coastguard Worker self.__data[row] = (self.__data[row][0], custom_properties) 543*77c1e3ccSAndroid Build Coastguard Worker 544*77c1e3ccSAndroid Build Coastguard Worker def LoadData(self, data, custom_properties=None): 545*77c1e3ccSAndroid Build Coastguard Worker """Loads new rows to the data table, clearing existing rows. 546*77c1e3ccSAndroid Build Coastguard Worker 547*77c1e3ccSAndroid Build Coastguard Worker May also set the custom_properties for the added rows. The given custom 548*77c1e3ccSAndroid Build Coastguard Worker properties dictionary specifies the dictionary that will be used for *all* 549*77c1e3ccSAndroid Build Coastguard Worker given rows. 550*77c1e3ccSAndroid Build Coastguard Worker 551*77c1e3ccSAndroid Build Coastguard Worker Args: 552*77c1e3ccSAndroid Build Coastguard Worker data: The rows that the table will contain. 553*77c1e3ccSAndroid Build Coastguard Worker custom_properties: A dictionary of string to string to set as the custom 554*77c1e3ccSAndroid Build Coastguard Worker properties for all rows. 555*77c1e3ccSAndroid Build Coastguard Worker """ 556*77c1e3ccSAndroid Build Coastguard Worker self.__data = [] 557*77c1e3ccSAndroid Build Coastguard Worker self.AppendData(data, custom_properties) 558*77c1e3ccSAndroid Build Coastguard Worker 559*77c1e3ccSAndroid Build Coastguard Worker def AppendData(self, data, custom_properties=None): 560*77c1e3ccSAndroid Build Coastguard Worker """Appends new data to the table. 561*77c1e3ccSAndroid Build Coastguard Worker 562*77c1e3ccSAndroid Build Coastguard Worker Data is appended in rows. Data must comply with 563*77c1e3ccSAndroid Build Coastguard Worker the table schema passed in to __init__(). See CoerceValue() for a list 564*77c1e3ccSAndroid Build Coastguard Worker of acceptable data types. See the class documentation for more information 565*77c1e3ccSAndroid Build Coastguard Worker and examples of schema and data values. 566*77c1e3ccSAndroid Build Coastguard Worker 567*77c1e3ccSAndroid Build Coastguard Worker Args: 568*77c1e3ccSAndroid Build Coastguard Worker data: The row to add to the table. The data must conform to the table 569*77c1e3ccSAndroid Build Coastguard Worker description format. 570*77c1e3ccSAndroid Build Coastguard Worker custom_properties: A dictionary of string to string, representing the 571*77c1e3ccSAndroid Build Coastguard Worker custom properties to add to all the rows. 572*77c1e3ccSAndroid Build Coastguard Worker 573*77c1e3ccSAndroid Build Coastguard Worker Raises: 574*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The data structure does not match the description. 575*77c1e3ccSAndroid Build Coastguard Worker """ 576*77c1e3ccSAndroid Build Coastguard Worker # If the maximal depth is 0, we simply iterate over the data table 577*77c1e3ccSAndroid Build Coastguard Worker # lines and insert them using _InnerAppendData. Otherwise, we simply 578*77c1e3ccSAndroid Build Coastguard Worker # let the _InnerAppendData handle all the levels. 579*77c1e3ccSAndroid Build Coastguard Worker if not self.__columns[-1]["depth"]: 580*77c1e3ccSAndroid Build Coastguard Worker for row in data: 581*77c1e3ccSAndroid Build Coastguard Worker self._InnerAppendData(({}, custom_properties), row, 0) 582*77c1e3ccSAndroid Build Coastguard Worker else: 583*77c1e3ccSAndroid Build Coastguard Worker self._InnerAppendData(({}, custom_properties), data, 0) 584*77c1e3ccSAndroid Build Coastguard Worker 585*77c1e3ccSAndroid Build Coastguard Worker def _InnerAppendData(self, prev_col_values, data, col_index): 586*77c1e3ccSAndroid Build Coastguard Worker """Inner function to assist LoadData.""" 587*77c1e3ccSAndroid Build Coastguard Worker # We first check that col_index has not exceeded the columns size 588*77c1e3ccSAndroid Build Coastguard Worker if col_index >= len(self.__columns): 589*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("The data does not match description, too deep") 590*77c1e3ccSAndroid Build Coastguard Worker 591*77c1e3ccSAndroid Build Coastguard Worker # Dealing with the scalar case, the data is the last value. 592*77c1e3ccSAndroid Build Coastguard Worker if self.__columns[col_index]["container"] == "scalar": 593*77c1e3ccSAndroid Build Coastguard Worker prev_col_values[0][self.__columns[col_index]["id"]] = data 594*77c1e3ccSAndroid Build Coastguard Worker self.__data.append(prev_col_values) 595*77c1e3ccSAndroid Build Coastguard Worker return 596*77c1e3ccSAndroid Build Coastguard Worker 597*77c1e3ccSAndroid Build Coastguard Worker if self.__columns[col_index]["container"] == "iter": 598*77c1e3ccSAndroid Build Coastguard Worker if not hasattr(data, "__iter__") or isinstance(data, dict): 599*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Expected iterable object, got %s" % 600*77c1e3ccSAndroid Build Coastguard Worker type(data)) 601*77c1e3ccSAndroid Build Coastguard Worker # We only need to insert the rest of the columns 602*77c1e3ccSAndroid Build Coastguard Worker # If there are less items than expected, we only add what there is. 603*77c1e3ccSAndroid Build Coastguard Worker for value in data: 604*77c1e3ccSAndroid Build Coastguard Worker if col_index >= len(self.__columns): 605*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Too many elements given in data") 606*77c1e3ccSAndroid Build Coastguard Worker prev_col_values[0][self.__columns[col_index]["id"]] = value 607*77c1e3ccSAndroid Build Coastguard Worker col_index += 1 608*77c1e3ccSAndroid Build Coastguard Worker self.__data.append(prev_col_values) 609*77c1e3ccSAndroid Build Coastguard Worker return 610*77c1e3ccSAndroid Build Coastguard Worker 611*77c1e3ccSAndroid Build Coastguard Worker # We know the current level is a dictionary, we verify the type. 612*77c1e3ccSAndroid Build Coastguard Worker if not isinstance(data, dict): 613*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Expected dictionary at current level, got %s" % 614*77c1e3ccSAndroid Build Coastguard Worker type(data)) 615*77c1e3ccSAndroid Build Coastguard Worker # We check if this is the last level 616*77c1e3ccSAndroid Build Coastguard Worker if self.__columns[col_index]["depth"] == self.__columns[-1]["depth"]: 617*77c1e3ccSAndroid Build Coastguard Worker # We need to add the keys in the dictionary as they are 618*77c1e3ccSAndroid Build Coastguard Worker for col in self.__columns[col_index:]: 619*77c1e3ccSAndroid Build Coastguard Worker if col["id"] in data: 620*77c1e3ccSAndroid Build Coastguard Worker prev_col_values[0][col["id"]] = data[col["id"]] 621*77c1e3ccSAndroid Build Coastguard Worker self.__data.append(prev_col_values) 622*77c1e3ccSAndroid Build Coastguard Worker return 623*77c1e3ccSAndroid Build Coastguard Worker 624*77c1e3ccSAndroid Build Coastguard Worker # We have a dictionary in an inner depth level. 625*77c1e3ccSAndroid Build Coastguard Worker if not data.keys(): 626*77c1e3ccSAndroid Build Coastguard Worker # In case this is an empty dictionary, we add a record with the columns 627*77c1e3ccSAndroid Build Coastguard Worker # filled only until this point. 628*77c1e3ccSAndroid Build Coastguard Worker self.__data.append(prev_col_values) 629*77c1e3ccSAndroid Build Coastguard Worker else: 630*77c1e3ccSAndroid Build Coastguard Worker for key in sorted(data): 631*77c1e3ccSAndroid Build Coastguard Worker col_values = dict(prev_col_values[0]) 632*77c1e3ccSAndroid Build Coastguard Worker col_values[self.__columns[col_index]["id"]] = key 633*77c1e3ccSAndroid Build Coastguard Worker self._InnerAppendData((col_values, prev_col_values[1]), 634*77c1e3ccSAndroid Build Coastguard Worker data[key], col_index + 1) 635*77c1e3ccSAndroid Build Coastguard Worker 636*77c1e3ccSAndroid Build Coastguard Worker def _PreparedData(self, order_by=()): 637*77c1e3ccSAndroid Build Coastguard Worker """Prepares the data for enumeration - sorting it by order_by. 638*77c1e3ccSAndroid Build Coastguard Worker 639*77c1e3ccSAndroid Build Coastguard Worker Args: 640*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Specifies the name of the column(s) to sort by, and 641*77c1e3ccSAndroid Build Coastguard Worker (optionally) which direction to sort in. Default sort direction 642*77c1e3ccSAndroid Build Coastguard Worker is asc. Following formats are accepted: 643*77c1e3ccSAndroid Build Coastguard Worker "string_col_name" -- For a single key in default (asc) order. 644*77c1e3ccSAndroid Build Coastguard Worker ("string_col_name", "asc|desc") -- For a single key. 645*77c1e3ccSAndroid Build Coastguard Worker [("col_1","asc|desc"), ("col_2","asc|desc")] -- For more than 646*77c1e3ccSAndroid Build Coastguard Worker one column, an array of tuples of (col_name, "asc|desc"). 647*77c1e3ccSAndroid Build Coastguard Worker 648*77c1e3ccSAndroid Build Coastguard Worker Returns: 649*77c1e3ccSAndroid Build Coastguard Worker The data sorted by the keys given. 650*77c1e3ccSAndroid Build Coastguard Worker 651*77c1e3ccSAndroid Build Coastguard Worker Raises: 652*77c1e3ccSAndroid Build Coastguard Worker DataTableException: Sort direction not in 'asc' or 'desc' 653*77c1e3ccSAndroid Build Coastguard Worker """ 654*77c1e3ccSAndroid Build Coastguard Worker if not order_by: 655*77c1e3ccSAndroid Build Coastguard Worker return self.__data 656*77c1e3ccSAndroid Build Coastguard Worker 657*77c1e3ccSAndroid Build Coastguard Worker proper_sort_keys = [] 658*77c1e3ccSAndroid Build Coastguard Worker if isinstance(order_by, types.StringTypes) or ( 659*77c1e3ccSAndroid Build Coastguard Worker isinstance(order_by, tuple) and len(order_by) == 2 and 660*77c1e3ccSAndroid Build Coastguard Worker order_by[1].lower() in ["asc", "desc"]): 661*77c1e3ccSAndroid Build Coastguard Worker order_by = (order_by,) 662*77c1e3ccSAndroid Build Coastguard Worker for key in order_by: 663*77c1e3ccSAndroid Build Coastguard Worker if isinstance(key, types.StringTypes): 664*77c1e3ccSAndroid Build Coastguard Worker proper_sort_keys.append((key, 1)) 665*77c1e3ccSAndroid Build Coastguard Worker elif (isinstance(key, (list, tuple)) and len(key) == 2 and 666*77c1e3ccSAndroid Build Coastguard Worker key[1].lower() in ("asc", "desc")): 667*77c1e3ccSAndroid Build Coastguard Worker proper_sort_keys.append((key[0], key[1].lower() == "asc" and 1 or -1)) 668*77c1e3ccSAndroid Build Coastguard Worker else: 669*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException("Expected tuple with second value: " 670*77c1e3ccSAndroid Build Coastguard Worker "'asc' or 'desc'") 671*77c1e3ccSAndroid Build Coastguard Worker 672*77c1e3ccSAndroid Build Coastguard Worker def SortCmpFunc(row1, row2): 673*77c1e3ccSAndroid Build Coastguard Worker """cmp function for sorted. Compares by keys and 'asc'/'desc' keywords.""" 674*77c1e3ccSAndroid Build Coastguard Worker for key, asc_mult in proper_sort_keys: 675*77c1e3ccSAndroid Build Coastguard Worker cmp_result = asc_mult * cmp(row1[0].get(key), row2[0].get(key)) 676*77c1e3ccSAndroid Build Coastguard Worker if cmp_result: 677*77c1e3ccSAndroid Build Coastguard Worker return cmp_result 678*77c1e3ccSAndroid Build Coastguard Worker return 0 679*77c1e3ccSAndroid Build Coastguard Worker 680*77c1e3ccSAndroid Build Coastguard Worker return sorted(self.__data, cmp=SortCmpFunc) 681*77c1e3ccSAndroid Build Coastguard Worker 682*77c1e3ccSAndroid Build Coastguard Worker def ToJSCode(self, name, columns_order=None, order_by=()): 683*77c1e3ccSAndroid Build Coastguard Worker """Writes the data table as a JS code string. 684*77c1e3ccSAndroid Build Coastguard Worker 685*77c1e3ccSAndroid Build Coastguard Worker This method writes a string of JS code that can be run to 686*77c1e3ccSAndroid Build Coastguard Worker generate a DataTable with the specified data. Typically used for debugging 687*77c1e3ccSAndroid Build Coastguard Worker only. 688*77c1e3ccSAndroid Build Coastguard Worker 689*77c1e3ccSAndroid Build Coastguard Worker Args: 690*77c1e3ccSAndroid Build Coastguard Worker name: The name of the table. The name would be used as the DataTable's 691*77c1e3ccSAndroid Build Coastguard Worker variable name in the created JS code. 692*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. Specifies the order of columns in the 693*77c1e3ccSAndroid Build Coastguard Worker output table. Specify a list of all column IDs in the order 694*77c1e3ccSAndroid Build Coastguard Worker in which you want the table created. 695*77c1e3ccSAndroid Build Coastguard Worker Note that you must list all column IDs in this parameter, 696*77c1e3ccSAndroid Build Coastguard Worker if you use it. 697*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Specifies the name of the column(s) to sort by. 698*77c1e3ccSAndroid Build Coastguard Worker Passed as is to _PreparedData. 699*77c1e3ccSAndroid Build Coastguard Worker 700*77c1e3ccSAndroid Build Coastguard Worker Returns: 701*77c1e3ccSAndroid Build Coastguard Worker A string of JS code that, when run, generates a DataTable with the given 702*77c1e3ccSAndroid Build Coastguard Worker name and the data stored in the DataTable object. 703*77c1e3ccSAndroid Build Coastguard Worker Example result: 704*77c1e3ccSAndroid Build Coastguard Worker "var tab1 = new google.visualization.DataTable(); 705*77c1e3ccSAndroid Build Coastguard Worker tab1.addColumn("string", "a", "a"); 706*77c1e3ccSAndroid Build Coastguard Worker tab1.addColumn("number", "b", "b"); 707*77c1e3ccSAndroid Build Coastguard Worker tab1.addColumn("boolean", "c", "c"); 708*77c1e3ccSAndroid Build Coastguard Worker tab1.addRows(10); 709*77c1e3ccSAndroid Build Coastguard Worker tab1.setCell(0, 0, "a"); 710*77c1e3ccSAndroid Build Coastguard Worker tab1.setCell(0, 1, 1, null, {"foo": "bar"}); 711*77c1e3ccSAndroid Build Coastguard Worker tab1.setCell(0, 2, true); 712*77c1e3ccSAndroid Build Coastguard Worker ... 713*77c1e3ccSAndroid Build Coastguard Worker tab1.setCell(9, 0, "c"); 714*77c1e3ccSAndroid Build Coastguard Worker tab1.setCell(9, 1, 3, "3$"); 715*77c1e3ccSAndroid Build Coastguard Worker tab1.setCell(9, 2, false);" 716*77c1e3ccSAndroid Build Coastguard Worker 717*77c1e3ccSAndroid Build Coastguard Worker Raises: 718*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The data does not match the type. 719*77c1e3ccSAndroid Build Coastguard Worker """ 720*77c1e3ccSAndroid Build Coastguard Worker 721*77c1e3ccSAndroid Build Coastguard Worker encoder = DataTableJSONEncoder() 722*77c1e3ccSAndroid Build Coastguard Worker 723*77c1e3ccSAndroid Build Coastguard Worker if columns_order is None: 724*77c1e3ccSAndroid Build Coastguard Worker columns_order = [col["id"] for col in self.__columns] 725*77c1e3ccSAndroid Build Coastguard Worker col_dict = dict([(col["id"], col) for col in self.__columns]) 726*77c1e3ccSAndroid Build Coastguard Worker 727*77c1e3ccSAndroid Build Coastguard Worker # We first create the table with the given name 728*77c1e3ccSAndroid Build Coastguard Worker jscode = "var %s = new google.visualization.DataTable();\n" % name 729*77c1e3ccSAndroid Build Coastguard Worker if self.custom_properties: 730*77c1e3ccSAndroid Build Coastguard Worker jscode += "%s.setTableProperties(%s);\n" % ( 731*77c1e3ccSAndroid Build Coastguard Worker name, encoder.encode(self.custom_properties)) 732*77c1e3ccSAndroid Build Coastguard Worker 733*77c1e3ccSAndroid Build Coastguard Worker # We add the columns to the table 734*77c1e3ccSAndroid Build Coastguard Worker for i, col in enumerate(columns_order): 735*77c1e3ccSAndroid Build Coastguard Worker jscode += "%s.addColumn(%s, %s, %s);\n" % ( 736*77c1e3ccSAndroid Build Coastguard Worker name, 737*77c1e3ccSAndroid Build Coastguard Worker encoder.encode(col_dict[col]["type"]), 738*77c1e3ccSAndroid Build Coastguard Worker encoder.encode(col_dict[col]["label"]), 739*77c1e3ccSAndroid Build Coastguard Worker encoder.encode(col_dict[col]["id"])) 740*77c1e3ccSAndroid Build Coastguard Worker if col_dict[col]["custom_properties"]: 741*77c1e3ccSAndroid Build Coastguard Worker jscode += "%s.setColumnProperties(%d, %s);\n" % ( 742*77c1e3ccSAndroid Build Coastguard Worker name, i, encoder.encode(col_dict[col]["custom_properties"])) 743*77c1e3ccSAndroid Build Coastguard Worker jscode += "%s.addRows(%d);\n" % (name, len(self.__data)) 744*77c1e3ccSAndroid Build Coastguard Worker 745*77c1e3ccSAndroid Build Coastguard Worker # We now go over the data and add each row 746*77c1e3ccSAndroid Build Coastguard Worker for (i, (row, cp)) in enumerate(self._PreparedData(order_by)): 747*77c1e3ccSAndroid Build Coastguard Worker # We add all the elements of this row by their order 748*77c1e3ccSAndroid Build Coastguard Worker for (j, col) in enumerate(columns_order): 749*77c1e3ccSAndroid Build Coastguard Worker if col not in row or row[col] is None: 750*77c1e3ccSAndroid Build Coastguard Worker continue 751*77c1e3ccSAndroid Build Coastguard Worker value = self.CoerceValue(row[col], col_dict[col]["type"]) 752*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, tuple): 753*77c1e3ccSAndroid Build Coastguard Worker cell_cp = "" 754*77c1e3ccSAndroid Build Coastguard Worker if len(value) == 3: 755*77c1e3ccSAndroid Build Coastguard Worker cell_cp = ", %s" % encoder.encode(row[col][2]) 756*77c1e3ccSAndroid Build Coastguard Worker # We have a formatted value or custom property as well 757*77c1e3ccSAndroid Build Coastguard Worker jscode += ("%s.setCell(%d, %d, %s, %s%s);\n" % 758*77c1e3ccSAndroid Build Coastguard Worker (name, i, j, 759*77c1e3ccSAndroid Build Coastguard Worker self.EscapeForJSCode(encoder, value[0]), 760*77c1e3ccSAndroid Build Coastguard Worker self.EscapeForJSCode(encoder, value[1]), cell_cp)) 761*77c1e3ccSAndroid Build Coastguard Worker else: 762*77c1e3ccSAndroid Build Coastguard Worker jscode += "%s.setCell(%d, %d, %s);\n" % ( 763*77c1e3ccSAndroid Build Coastguard Worker name, i, j, self.EscapeForJSCode(encoder, value)) 764*77c1e3ccSAndroid Build Coastguard Worker if cp: 765*77c1e3ccSAndroid Build Coastguard Worker jscode += "%s.setRowProperties(%d, %s);\n" % ( 766*77c1e3ccSAndroid Build Coastguard Worker name, i, encoder.encode(cp)) 767*77c1e3ccSAndroid Build Coastguard Worker return jscode 768*77c1e3ccSAndroid Build Coastguard Worker 769*77c1e3ccSAndroid Build Coastguard Worker def ToHtml(self, columns_order=None, order_by=()): 770*77c1e3ccSAndroid Build Coastguard Worker """Writes the data table as an HTML table code string. 771*77c1e3ccSAndroid Build Coastguard Worker 772*77c1e3ccSAndroid Build Coastguard Worker Args: 773*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. Specifies the order of columns in the 774*77c1e3ccSAndroid Build Coastguard Worker output table. Specify a list of all column IDs in the order 775*77c1e3ccSAndroid Build Coastguard Worker in which you want the table created. 776*77c1e3ccSAndroid Build Coastguard Worker Note that you must list all column IDs in this parameter, 777*77c1e3ccSAndroid Build Coastguard Worker if you use it. 778*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Specifies the name of the column(s) to sort by. 779*77c1e3ccSAndroid Build Coastguard Worker Passed as is to _PreparedData. 780*77c1e3ccSAndroid Build Coastguard Worker 781*77c1e3ccSAndroid Build Coastguard Worker Returns: 782*77c1e3ccSAndroid Build Coastguard Worker An HTML table code string. 783*77c1e3ccSAndroid Build Coastguard Worker Example result (the result is without the newlines): 784*77c1e3ccSAndroid Build Coastguard Worker <html><body><table border="1"> 785*77c1e3ccSAndroid Build Coastguard Worker <thead><tr><th>a</th><th>b</th><th>c</th></tr></thead> 786*77c1e3ccSAndroid Build Coastguard Worker <tbody> 787*77c1e3ccSAndroid Build Coastguard Worker <tr><td>1</td><td>"z"</td><td>2</td></tr> 788*77c1e3ccSAndroid Build Coastguard Worker <tr><td>"3$"</td><td>"w"</td><td></td></tr> 789*77c1e3ccSAndroid Build Coastguard Worker </tbody> 790*77c1e3ccSAndroid Build Coastguard Worker </table></body></html> 791*77c1e3ccSAndroid Build Coastguard Worker 792*77c1e3ccSAndroid Build Coastguard Worker Raises: 793*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The data does not match the type. 794*77c1e3ccSAndroid Build Coastguard Worker """ 795*77c1e3ccSAndroid Build Coastguard Worker table_template = "<html><body><table border=\"1\">%s</table></body></html>" 796*77c1e3ccSAndroid Build Coastguard Worker columns_template = "<thead><tr>%s</tr></thead>" 797*77c1e3ccSAndroid Build Coastguard Worker rows_template = "<tbody>%s</tbody>" 798*77c1e3ccSAndroid Build Coastguard Worker row_template = "<tr>%s</tr>" 799*77c1e3ccSAndroid Build Coastguard Worker header_cell_template = "<th>%s</th>" 800*77c1e3ccSAndroid Build Coastguard Worker cell_template = "<td>%s</td>" 801*77c1e3ccSAndroid Build Coastguard Worker 802*77c1e3ccSAndroid Build Coastguard Worker if columns_order is None: 803*77c1e3ccSAndroid Build Coastguard Worker columns_order = [col["id"] for col in self.__columns] 804*77c1e3ccSAndroid Build Coastguard Worker col_dict = dict([(col["id"], col) for col in self.__columns]) 805*77c1e3ccSAndroid Build Coastguard Worker 806*77c1e3ccSAndroid Build Coastguard Worker columns_list = [] 807*77c1e3ccSAndroid Build Coastguard Worker for col in columns_order: 808*77c1e3ccSAndroid Build Coastguard Worker columns_list.append(header_cell_template % 809*77c1e3ccSAndroid Build Coastguard Worker cgi.escape(col_dict[col]["label"])) 810*77c1e3ccSAndroid Build Coastguard Worker columns_html = columns_template % "".join(columns_list) 811*77c1e3ccSAndroid Build Coastguard Worker 812*77c1e3ccSAndroid Build Coastguard Worker rows_list = [] 813*77c1e3ccSAndroid Build Coastguard Worker # We now go over the data and add each row 814*77c1e3ccSAndroid Build Coastguard Worker for row, unused_cp in self._PreparedData(order_by): 815*77c1e3ccSAndroid Build Coastguard Worker cells_list = [] 816*77c1e3ccSAndroid Build Coastguard Worker # We add all the elements of this row by their order 817*77c1e3ccSAndroid Build Coastguard Worker for col in columns_order: 818*77c1e3ccSAndroid Build Coastguard Worker # For empty string we want empty quotes (""). 819*77c1e3ccSAndroid Build Coastguard Worker value = "" 820*77c1e3ccSAndroid Build Coastguard Worker if col in row and row[col] is not None: 821*77c1e3ccSAndroid Build Coastguard Worker value = self.CoerceValue(row[col], col_dict[col]["type"]) 822*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, tuple): 823*77c1e3ccSAndroid Build Coastguard Worker # We have a formatted value and we're going to use it 824*77c1e3ccSAndroid Build Coastguard Worker cells_list.append(cell_template % cgi.escape(self.ToString(value[1]))) 825*77c1e3ccSAndroid Build Coastguard Worker else: 826*77c1e3ccSAndroid Build Coastguard Worker cells_list.append(cell_template % cgi.escape(self.ToString(value))) 827*77c1e3ccSAndroid Build Coastguard Worker rows_list.append(row_template % "".join(cells_list)) 828*77c1e3ccSAndroid Build Coastguard Worker rows_html = rows_template % "".join(rows_list) 829*77c1e3ccSAndroid Build Coastguard Worker 830*77c1e3ccSAndroid Build Coastguard Worker return table_template % (columns_html + rows_html) 831*77c1e3ccSAndroid Build Coastguard Worker 832*77c1e3ccSAndroid Build Coastguard Worker def ToCsv(self, columns_order=None, order_by=(), separator=","): 833*77c1e3ccSAndroid Build Coastguard Worker """Writes the data table as a CSV string. 834*77c1e3ccSAndroid Build Coastguard Worker 835*77c1e3ccSAndroid Build Coastguard Worker Output is encoded in UTF-8 because the Python "csv" module can't handle 836*77c1e3ccSAndroid Build Coastguard Worker Unicode properly according to its documentation. 837*77c1e3ccSAndroid Build Coastguard Worker 838*77c1e3ccSAndroid Build Coastguard Worker Args: 839*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. Specifies the order of columns in the 840*77c1e3ccSAndroid Build Coastguard Worker output table. Specify a list of all column IDs in the order 841*77c1e3ccSAndroid Build Coastguard Worker in which you want the table created. 842*77c1e3ccSAndroid Build Coastguard Worker Note that you must list all column IDs in this parameter, 843*77c1e3ccSAndroid Build Coastguard Worker if you use it. 844*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Specifies the name of the column(s) to sort by. 845*77c1e3ccSAndroid Build Coastguard Worker Passed as is to _PreparedData. 846*77c1e3ccSAndroid Build Coastguard Worker separator: Optional. The separator to use between the values. 847*77c1e3ccSAndroid Build Coastguard Worker 848*77c1e3ccSAndroid Build Coastguard Worker Returns: 849*77c1e3ccSAndroid Build Coastguard Worker A CSV string representing the table. 850*77c1e3ccSAndroid Build Coastguard Worker Example result: 851*77c1e3ccSAndroid Build Coastguard Worker 'a','b','c' 852*77c1e3ccSAndroid Build Coastguard Worker 1,'z',2 853*77c1e3ccSAndroid Build Coastguard Worker 3,'w','' 854*77c1e3ccSAndroid Build Coastguard Worker 855*77c1e3ccSAndroid Build Coastguard Worker Raises: 856*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The data does not match the type. 857*77c1e3ccSAndroid Build Coastguard Worker """ 858*77c1e3ccSAndroid Build Coastguard Worker 859*77c1e3ccSAndroid Build Coastguard Worker csv_buffer = cStringIO.StringIO() 860*77c1e3ccSAndroid Build Coastguard Worker writer = csv.writer(csv_buffer, delimiter=separator) 861*77c1e3ccSAndroid Build Coastguard Worker 862*77c1e3ccSAndroid Build Coastguard Worker if columns_order is None: 863*77c1e3ccSAndroid Build Coastguard Worker columns_order = [col["id"] for col in self.__columns] 864*77c1e3ccSAndroid Build Coastguard Worker col_dict = dict([(col["id"], col) for col in self.__columns]) 865*77c1e3ccSAndroid Build Coastguard Worker 866*77c1e3ccSAndroid Build Coastguard Worker writer.writerow([col_dict[col]["label"].encode("utf-8") 867*77c1e3ccSAndroid Build Coastguard Worker for col in columns_order]) 868*77c1e3ccSAndroid Build Coastguard Worker 869*77c1e3ccSAndroid Build Coastguard Worker # We now go over the data and add each row 870*77c1e3ccSAndroid Build Coastguard Worker for row, unused_cp in self._PreparedData(order_by): 871*77c1e3ccSAndroid Build Coastguard Worker cells_list = [] 872*77c1e3ccSAndroid Build Coastguard Worker # We add all the elements of this row by their order 873*77c1e3ccSAndroid Build Coastguard Worker for col in columns_order: 874*77c1e3ccSAndroid Build Coastguard Worker value = "" 875*77c1e3ccSAndroid Build Coastguard Worker if col in row and row[col] is not None: 876*77c1e3ccSAndroid Build Coastguard Worker value = self.CoerceValue(row[col], col_dict[col]["type"]) 877*77c1e3ccSAndroid Build Coastguard Worker if isinstance(value, tuple): 878*77c1e3ccSAndroid Build Coastguard Worker # We have a formatted value. Using it only for date/time types. 879*77c1e3ccSAndroid Build Coastguard Worker if col_dict[col]["type"] in ["date", "datetime", "timeofday"]: 880*77c1e3ccSAndroid Build Coastguard Worker cells_list.append(self.ToString(value[1]).encode("utf-8")) 881*77c1e3ccSAndroid Build Coastguard Worker else: 882*77c1e3ccSAndroid Build Coastguard Worker cells_list.append(self.ToString(value[0]).encode("utf-8")) 883*77c1e3ccSAndroid Build Coastguard Worker else: 884*77c1e3ccSAndroid Build Coastguard Worker cells_list.append(self.ToString(value).encode("utf-8")) 885*77c1e3ccSAndroid Build Coastguard Worker writer.writerow(cells_list) 886*77c1e3ccSAndroid Build Coastguard Worker return csv_buffer.getvalue() 887*77c1e3ccSAndroid Build Coastguard Worker 888*77c1e3ccSAndroid Build Coastguard Worker def ToTsvExcel(self, columns_order=None, order_by=()): 889*77c1e3ccSAndroid Build Coastguard Worker """Returns a file in tab-separated-format readable by MS Excel. 890*77c1e3ccSAndroid Build Coastguard Worker 891*77c1e3ccSAndroid Build Coastguard Worker Returns a file in UTF-16 little endian encoding, with tabs separating the 892*77c1e3ccSAndroid Build Coastguard Worker values. 893*77c1e3ccSAndroid Build Coastguard Worker 894*77c1e3ccSAndroid Build Coastguard Worker Args: 895*77c1e3ccSAndroid Build Coastguard Worker columns_order: Delegated to ToCsv. 896*77c1e3ccSAndroid Build Coastguard Worker order_by: Delegated to ToCsv. 897*77c1e3ccSAndroid Build Coastguard Worker 898*77c1e3ccSAndroid Build Coastguard Worker Returns: 899*77c1e3ccSAndroid Build Coastguard Worker A tab-separated little endian UTF16 file representing the table. 900*77c1e3ccSAndroid Build Coastguard Worker """ 901*77c1e3ccSAndroid Build Coastguard Worker return (self.ToCsv(columns_order, order_by, separator="\t") 902*77c1e3ccSAndroid Build Coastguard Worker .decode("utf-8").encode("UTF-16LE")) 903*77c1e3ccSAndroid Build Coastguard Worker 904*77c1e3ccSAndroid Build Coastguard Worker def _ToJSonObj(self, columns_order=None, order_by=()): 905*77c1e3ccSAndroid Build Coastguard Worker """Returns an object suitable to be converted to JSON. 906*77c1e3ccSAndroid Build Coastguard Worker 907*77c1e3ccSAndroid Build Coastguard Worker Args: 908*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. A list of all column IDs in the order in which 909*77c1e3ccSAndroid Build Coastguard Worker you want them created in the output table. If specified, 910*77c1e3ccSAndroid Build Coastguard Worker all column IDs must be present. 911*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Specifies the name of the column(s) to sort by. 912*77c1e3ccSAndroid Build Coastguard Worker Passed as is to _PreparedData(). 913*77c1e3ccSAndroid Build Coastguard Worker 914*77c1e3ccSAndroid Build Coastguard Worker Returns: 915*77c1e3ccSAndroid Build Coastguard Worker A dictionary object for use by ToJSon or ToJSonResponse. 916*77c1e3ccSAndroid Build Coastguard Worker """ 917*77c1e3ccSAndroid Build Coastguard Worker if columns_order is None: 918*77c1e3ccSAndroid Build Coastguard Worker columns_order = [col["id"] for col in self.__columns] 919*77c1e3ccSAndroid Build Coastguard Worker col_dict = dict([(col["id"], col) for col in self.__columns]) 920*77c1e3ccSAndroid Build Coastguard Worker 921*77c1e3ccSAndroid Build Coastguard Worker # Creating the column JSON objects 922*77c1e3ccSAndroid Build Coastguard Worker col_objs = [] 923*77c1e3ccSAndroid Build Coastguard Worker for col_id in columns_order: 924*77c1e3ccSAndroid Build Coastguard Worker col_obj = {"id": col_dict[col_id]["id"], 925*77c1e3ccSAndroid Build Coastguard Worker "label": col_dict[col_id]["label"], 926*77c1e3ccSAndroid Build Coastguard Worker "type": col_dict[col_id]["type"]} 927*77c1e3ccSAndroid Build Coastguard Worker if col_dict[col_id]["custom_properties"]: 928*77c1e3ccSAndroid Build Coastguard Worker col_obj["p"] = col_dict[col_id]["custom_properties"] 929*77c1e3ccSAndroid Build Coastguard Worker col_objs.append(col_obj) 930*77c1e3ccSAndroid Build Coastguard Worker 931*77c1e3ccSAndroid Build Coastguard Worker # Creating the rows jsons 932*77c1e3ccSAndroid Build Coastguard Worker row_objs = [] 933*77c1e3ccSAndroid Build Coastguard Worker for row, cp in self._PreparedData(order_by): 934*77c1e3ccSAndroid Build Coastguard Worker cell_objs = [] 935*77c1e3ccSAndroid Build Coastguard Worker for col in columns_order: 936*77c1e3ccSAndroid Build Coastguard Worker value = self.CoerceValue(row.get(col, None), col_dict[col]["type"]) 937*77c1e3ccSAndroid Build Coastguard Worker if value is None: 938*77c1e3ccSAndroid Build Coastguard Worker cell_obj = None 939*77c1e3ccSAndroid Build Coastguard Worker elif isinstance(value, tuple): 940*77c1e3ccSAndroid Build Coastguard Worker cell_obj = {"v": value[0]} 941*77c1e3ccSAndroid Build Coastguard Worker if len(value) > 1 and value[1] is not None: 942*77c1e3ccSAndroid Build Coastguard Worker cell_obj["f"] = value[1] 943*77c1e3ccSAndroid Build Coastguard Worker if len(value) == 3: 944*77c1e3ccSAndroid Build Coastguard Worker cell_obj["p"] = value[2] 945*77c1e3ccSAndroid Build Coastguard Worker else: 946*77c1e3ccSAndroid Build Coastguard Worker cell_obj = {"v": value} 947*77c1e3ccSAndroid Build Coastguard Worker cell_objs.append(cell_obj) 948*77c1e3ccSAndroid Build Coastguard Worker row_obj = {"c": cell_objs} 949*77c1e3ccSAndroid Build Coastguard Worker if cp: 950*77c1e3ccSAndroid Build Coastguard Worker row_obj["p"] = cp 951*77c1e3ccSAndroid Build Coastguard Worker row_objs.append(row_obj) 952*77c1e3ccSAndroid Build Coastguard Worker 953*77c1e3ccSAndroid Build Coastguard Worker json_obj = {"cols": col_objs, "rows": row_objs} 954*77c1e3ccSAndroid Build Coastguard Worker if self.custom_properties: 955*77c1e3ccSAndroid Build Coastguard Worker json_obj["p"] = self.custom_properties 956*77c1e3ccSAndroid Build Coastguard Worker 957*77c1e3ccSAndroid Build Coastguard Worker return json_obj 958*77c1e3ccSAndroid Build Coastguard Worker 959*77c1e3ccSAndroid Build Coastguard Worker def ToJSon(self, columns_order=None, order_by=()): 960*77c1e3ccSAndroid Build Coastguard Worker """Returns a string that can be used in a JS DataTable constructor. 961*77c1e3ccSAndroid Build Coastguard Worker 962*77c1e3ccSAndroid Build Coastguard Worker This method writes a JSON string that can be passed directly into a Google 963*77c1e3ccSAndroid Build Coastguard Worker Visualization API DataTable constructor. Use this output if you are 964*77c1e3ccSAndroid Build Coastguard Worker hosting the visualization HTML on your site, and want to code the data 965*77c1e3ccSAndroid Build Coastguard Worker table in Python. Pass this string into the 966*77c1e3ccSAndroid Build Coastguard Worker google.visualization.DataTable constructor, e.g,: 967*77c1e3ccSAndroid Build Coastguard Worker ... on my page that hosts my visualization ... 968*77c1e3ccSAndroid Build Coastguard Worker google.setOnLoadCallback(drawTable); 969*77c1e3ccSAndroid Build Coastguard Worker function drawTable() { 970*77c1e3ccSAndroid Build Coastguard Worker var data = new google.visualization.DataTable(_my_JSon_string, 0.6); 971*77c1e3ccSAndroid Build Coastguard Worker myTable.draw(data); 972*77c1e3ccSAndroid Build Coastguard Worker } 973*77c1e3ccSAndroid Build Coastguard Worker 974*77c1e3ccSAndroid Build Coastguard Worker Args: 975*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. Specifies the order of columns in the 976*77c1e3ccSAndroid Build Coastguard Worker output table. Specify a list of all column IDs in the order 977*77c1e3ccSAndroid Build Coastguard Worker in which you want the table created. 978*77c1e3ccSAndroid Build Coastguard Worker Note that you must list all column IDs in this parameter, 979*77c1e3ccSAndroid Build Coastguard Worker if you use it. 980*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Specifies the name of the column(s) to sort by. 981*77c1e3ccSAndroid Build Coastguard Worker Passed as is to _PreparedData(). 982*77c1e3ccSAndroid Build Coastguard Worker 983*77c1e3ccSAndroid Build Coastguard Worker Returns: 984*77c1e3ccSAndroid Build Coastguard Worker A JSon constructor string to generate a JS DataTable with the data 985*77c1e3ccSAndroid Build Coastguard Worker stored in the DataTable object. 986*77c1e3ccSAndroid Build Coastguard Worker Example result (the result is without the newlines): 987*77c1e3ccSAndroid Build Coastguard Worker {cols: [{id:"a",label:"a",type:"number"}, 988*77c1e3ccSAndroid Build Coastguard Worker {id:"b",label:"b",type:"string"}, 989*77c1e3ccSAndroid Build Coastguard Worker {id:"c",label:"c",type:"number"}], 990*77c1e3ccSAndroid Build Coastguard Worker rows: [{c:[{v:1},{v:"z"},{v:2}]}, c:{[{v:3,f:"3$"},{v:"w"},{v:null}]}], 991*77c1e3ccSAndroid Build Coastguard Worker p: {'foo': 'bar'}} 992*77c1e3ccSAndroid Build Coastguard Worker 993*77c1e3ccSAndroid Build Coastguard Worker Raises: 994*77c1e3ccSAndroid Build Coastguard Worker DataTableException: The data does not match the type. 995*77c1e3ccSAndroid Build Coastguard Worker """ 996*77c1e3ccSAndroid Build Coastguard Worker 997*77c1e3ccSAndroid Build Coastguard Worker encoder = DataTableJSONEncoder() 998*77c1e3ccSAndroid Build Coastguard Worker return encoder.encode( 999*77c1e3ccSAndroid Build Coastguard Worker self._ToJSonObj(columns_order, order_by)).encode("utf-8") 1000*77c1e3ccSAndroid Build Coastguard Worker 1001*77c1e3ccSAndroid Build Coastguard Worker def ToJSonResponse(self, columns_order=None, order_by=(), req_id=0, 1002*77c1e3ccSAndroid Build Coastguard Worker response_handler="google.visualization.Query.setResponse"): 1003*77c1e3ccSAndroid Build Coastguard Worker """Writes a table as a JSON response that can be returned as-is to a client. 1004*77c1e3ccSAndroid Build Coastguard Worker 1005*77c1e3ccSAndroid Build Coastguard Worker This method writes a JSON response to return to a client in response to a 1006*77c1e3ccSAndroid Build Coastguard Worker Google Visualization API query. This string can be processed by the calling 1007*77c1e3ccSAndroid Build Coastguard Worker page, and is used to deliver a data table to a visualization hosted on 1008*77c1e3ccSAndroid Build Coastguard Worker a different page. 1009*77c1e3ccSAndroid Build Coastguard Worker 1010*77c1e3ccSAndroid Build Coastguard Worker Args: 1011*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. Passed straight to self.ToJSon(). 1012*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Passed straight to self.ToJSon(). 1013*77c1e3ccSAndroid Build Coastguard Worker req_id: Optional. The response id, as retrieved by the request. 1014*77c1e3ccSAndroid Build Coastguard Worker response_handler: Optional. The response handler, as retrieved by the 1015*77c1e3ccSAndroid Build Coastguard Worker request. 1016*77c1e3ccSAndroid Build Coastguard Worker 1017*77c1e3ccSAndroid Build Coastguard Worker Returns: 1018*77c1e3ccSAndroid Build Coastguard Worker A JSON response string to be received by JS the visualization Query 1019*77c1e3ccSAndroid Build Coastguard Worker object. This response would be translated into a DataTable on the 1020*77c1e3ccSAndroid Build Coastguard Worker client side. 1021*77c1e3ccSAndroid Build Coastguard Worker Example result (newlines added for readability): 1022*77c1e3ccSAndroid Build Coastguard Worker google.visualization.Query.setResponse({ 1023*77c1e3ccSAndroid Build Coastguard Worker 'version':'0.6', 'reqId':'0', 'status':'OK', 1024*77c1e3ccSAndroid Build Coastguard Worker 'table': {cols: [...], rows: [...]}}); 1025*77c1e3ccSAndroid Build Coastguard Worker 1026*77c1e3ccSAndroid Build Coastguard Worker Note: The URL returning this string can be used as a data source by Google 1027*77c1e3ccSAndroid Build Coastguard Worker Visualization Gadgets or from JS code. 1028*77c1e3ccSAndroid Build Coastguard Worker """ 1029*77c1e3ccSAndroid Build Coastguard Worker 1030*77c1e3ccSAndroid Build Coastguard Worker response_obj = { 1031*77c1e3ccSAndroid Build Coastguard Worker "version": "0.6", 1032*77c1e3ccSAndroid Build Coastguard Worker "reqId": str(req_id), 1033*77c1e3ccSAndroid Build Coastguard Worker "table": self._ToJSonObj(columns_order, order_by), 1034*77c1e3ccSAndroid Build Coastguard Worker "status": "ok" 1035*77c1e3ccSAndroid Build Coastguard Worker } 1036*77c1e3ccSAndroid Build Coastguard Worker encoder = DataTableJSONEncoder() 1037*77c1e3ccSAndroid Build Coastguard Worker return "%s(%s);" % (response_handler, 1038*77c1e3ccSAndroid Build Coastguard Worker encoder.encode(response_obj).encode("utf-8")) 1039*77c1e3ccSAndroid Build Coastguard Worker 1040*77c1e3ccSAndroid Build Coastguard Worker def ToResponse(self, columns_order=None, order_by=(), tqx=""): 1041*77c1e3ccSAndroid Build Coastguard Worker """Writes the right response according to the request string passed in tqx. 1042*77c1e3ccSAndroid Build Coastguard Worker 1043*77c1e3ccSAndroid Build Coastguard Worker This method parses the tqx request string (format of which is defined in 1044*77c1e3ccSAndroid Build Coastguard Worker the documentation for implementing a data source of Google Visualization), 1045*77c1e3ccSAndroid Build Coastguard Worker and returns the right response according to the request. 1046*77c1e3ccSAndroid Build Coastguard Worker It parses out the "out" parameter of tqx, calls the relevant response 1047*77c1e3ccSAndroid Build Coastguard Worker (ToJSonResponse() for "json", ToCsv() for "csv", ToHtml() for "html", 1048*77c1e3ccSAndroid Build Coastguard Worker ToTsvExcel() for "tsv-excel") and passes the response function the rest of 1049*77c1e3ccSAndroid Build Coastguard Worker the relevant request keys. 1050*77c1e3ccSAndroid Build Coastguard Worker 1051*77c1e3ccSAndroid Build Coastguard Worker Args: 1052*77c1e3ccSAndroid Build Coastguard Worker columns_order: Optional. Passed as is to the relevant response function. 1053*77c1e3ccSAndroid Build Coastguard Worker order_by: Optional. Passed as is to the relevant response function. 1054*77c1e3ccSAndroid Build Coastguard Worker tqx: Optional. The request string as received by HTTP GET. Should be in 1055*77c1e3ccSAndroid Build Coastguard Worker the format "key1:value1;key2:value2...". All keys have a default 1056*77c1e3ccSAndroid Build Coastguard Worker value, so an empty string will just do the default (which is calling 1057*77c1e3ccSAndroid Build Coastguard Worker ToJSonResponse() with no extra parameters). 1058*77c1e3ccSAndroid Build Coastguard Worker 1059*77c1e3ccSAndroid Build Coastguard Worker Returns: 1060*77c1e3ccSAndroid Build Coastguard Worker A response string, as returned by the relevant response function. 1061*77c1e3ccSAndroid Build Coastguard Worker 1062*77c1e3ccSAndroid Build Coastguard Worker Raises: 1063*77c1e3ccSAndroid Build Coastguard Worker DataTableException: One of the parameters passed in tqx is not supported. 1064*77c1e3ccSAndroid Build Coastguard Worker """ 1065*77c1e3ccSAndroid Build Coastguard Worker tqx_dict = {} 1066*77c1e3ccSAndroid Build Coastguard Worker if tqx: 1067*77c1e3ccSAndroid Build Coastguard Worker tqx_dict = dict(opt.split(":") for opt in tqx.split(";")) 1068*77c1e3ccSAndroid Build Coastguard Worker if tqx_dict.get("version", "0.6") != "0.6": 1069*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException( 1070*77c1e3ccSAndroid Build Coastguard Worker "Version (%s) passed by request is not supported." 1071*77c1e3ccSAndroid Build Coastguard Worker % tqx_dict["version"]) 1072*77c1e3ccSAndroid Build Coastguard Worker 1073*77c1e3ccSAndroid Build Coastguard Worker if tqx_dict.get("out", "json") == "json": 1074*77c1e3ccSAndroid Build Coastguard Worker response_handler = tqx_dict.get("responseHandler", 1075*77c1e3ccSAndroid Build Coastguard Worker "google.visualization.Query.setResponse") 1076*77c1e3ccSAndroid Build Coastguard Worker return self.ToJSonResponse(columns_order, order_by, 1077*77c1e3ccSAndroid Build Coastguard Worker req_id=tqx_dict.get("reqId", 0), 1078*77c1e3ccSAndroid Build Coastguard Worker response_handler=response_handler) 1079*77c1e3ccSAndroid Build Coastguard Worker elif tqx_dict["out"] == "html": 1080*77c1e3ccSAndroid Build Coastguard Worker return self.ToHtml(columns_order, order_by) 1081*77c1e3ccSAndroid Build Coastguard Worker elif tqx_dict["out"] == "csv": 1082*77c1e3ccSAndroid Build Coastguard Worker return self.ToCsv(columns_order, order_by) 1083*77c1e3ccSAndroid Build Coastguard Worker elif tqx_dict["out"] == "tsv-excel": 1084*77c1e3ccSAndroid Build Coastguard Worker return self.ToTsvExcel(columns_order, order_by) 1085*77c1e3ccSAndroid Build Coastguard Worker else: 1086*77c1e3ccSAndroid Build Coastguard Worker raise DataTableException( 1087*77c1e3ccSAndroid Build Coastguard Worker "'out' parameter: '%s' is not supported" % tqx_dict["out"]) 1088