xref: /aosp_15_r20/external/libaom/test/gviz_api.py (revision 77c1e3ccc04c968bd2bc212e87364f250e820521)
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