diff options
Diffstat (limited to 'third_party/aom/test/gviz_api.py')
-rwxr-xr-x | third_party/aom/test/gviz_api.py | 1087 |
1 files changed, 0 insertions, 1087 deletions
diff --git a/third_party/aom/test/gviz_api.py b/third_party/aom/test/gviz_api.py deleted file mode 100755 index d3a443dab..000000000 --- a/third_party/aom/test/gviz_api.py +++ /dev/null @@ -1,1087 +0,0 @@ -#!/usr/bin/python -# -# Copyright (c) 2016, Alliance for Open Media. All rights reserved -# -# This source code is subject to the terms of the BSD 2 Clause License and -# the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License -# was not distributed with this source code in the LICENSE file, you can -# obtain it at www.aomedia.org/license/software. If the Alliance for Open -# Media Patent License 1.0 was not distributed with this source code in the -# PATENTS file, you can obtain it at www.aomedia.org/license/patent. -# - -"""Converts Python data into data for Google Visualization API clients. - -This library can be used to create a google.visualization.DataTable usable by -visualizations built on the Google Visualization API. Output formats are raw -JSON, JSON response, JavaScript, CSV, and HTML table. - -See http://code.google.com/apis/visualization/ for documentation on the -Google Visualization API. -""" - -__author__ = "Amit Weinstein, Misha Seltzer, Jacob Baskin" - -import cgi -import cStringIO -import csv -import datetime -try: - import json -except ImportError: - import simplejson as json -import types - - -class DataTableException(Exception): - """The general exception object thrown by DataTable.""" - pass - - -class DataTableJSONEncoder(json.JSONEncoder): - """JSON encoder that handles date/time/datetime objects correctly.""" - - def __init__(self): - json.JSONEncoder.__init__(self, - separators=(",", ":"), - ensure_ascii=False) - - def default(self, o): - if isinstance(o, datetime.datetime): - if o.microsecond == 0: - # If the time doesn't have ms-resolution, leave it out to keep - # things smaller. - return "Date(%d,%d,%d,%d,%d,%d)" % ( - o.year, o.month - 1, o.day, o.hour, o.minute, o.second) - else: - return "Date(%d,%d,%d,%d,%d,%d,%d)" % ( - o.year, o.month - 1, o.day, o.hour, o.minute, o.second, - o.microsecond / 1000) - elif isinstance(o, datetime.date): - return "Date(%d,%d,%d)" % (o.year, o.month - 1, o.day) - elif isinstance(o, datetime.time): - return [o.hour, o.minute, o.second] - else: - return super(DataTableJSONEncoder, self).default(o) - - -class DataTable(object): - """Wraps the data to convert to a Google Visualization API DataTable. - - Create this object, populate it with data, then call one of the ToJS... - methods to return a string representation of the data in the format described. - - You can clear all data from the object to reuse it, but you cannot clear - individual cells, rows, or columns. You also cannot modify the table schema - specified in the class constructor. - - You can add new data one or more rows at a time. All data added to an - instantiated DataTable must conform to the schema passed in to __init__(). - - You can reorder the columns in the output table, and also specify row sorting - order by column. The default column order is according to the original - table_description parameter. Default row sort order is ascending, by column - 1 values. For a dictionary, we sort the keys for order. - - The data and the table_description are closely tied, as described here: - - The table schema is defined in the class constructor's table_description - parameter. The user defines each column using a tuple of - (id[, type[, label[, custom_properties]]]). The default value for type is - string, label is the same as ID if not specified, and custom properties is - an empty dictionary if not specified. - - table_description is a dictionary or list, containing one or more column - descriptor tuples, nested dictionaries, and lists. Each dictionary key, list - element, or dictionary element must eventually be defined as - a column description tuple. Here's an example of a dictionary where the key - is a tuple, and the value is a list of two tuples: - {('a', 'number'): [('b', 'number'), ('c', 'string')]} - - This flexibility in data entry enables you to build and manipulate your data - in a Python structure that makes sense for your program. - - Add data to the table using the same nested design as the table's - table_description, replacing column descriptor tuples with cell data, and - each row is an element in the top level collection. This will be a bit - clearer after you look at the following examples showing the - table_description, matching data, and the resulting table: - - Columns as list of tuples [col1, col2, col3] - table_description: [('a', 'number'), ('b', 'string')] - AppendData( [[1, 'z'], [2, 'w'], [4, 'o'], [5, 'k']] ) - Table: - a b <--- these are column ids/labels - 1 z - 2 w - 4 o - 5 k - - Dictionary of columns, where key is a column, and value is a list of - columns {col1: [col2, col3]} - table_description: {('a', 'number'): [('b', 'number'), ('c', 'string')]} - AppendData( data: {1: [2, 'z'], 3: [4, 'w']} - Table: - a b c - 1 2 z - 3 4 w - - Dictionary where key is a column, and the value is itself a dictionary of - columns {col1: {col2, col3}} - table_description: {('a', 'number'): {'b': 'number', 'c': 'string'}} - AppendData( data: {1: {'b': 2, 'c': 'z'}, 3: {'b': 4, 'c': 'w'}} - Table: - a b c - 1 2 z - 3 4 w - """ - - def __init__(self, table_description, data=None, custom_properties=None): - """Initialize the data table from a table schema and (optionally) data. - - See the class documentation for more information on table schema and data - values. - - Args: - table_description: A table schema, following one of the formats described - in TableDescriptionParser(). Schemas describe the - column names, data types, and labels. See - TableDescriptionParser() for acceptable formats. - data: Optional. If given, fills the table with the given data. The data - structure must be consistent with schema in table_description. See - the class documentation for more information on acceptable data. You - can add data later by calling AppendData(). - custom_properties: Optional. A dictionary from string to string that - goes into the table's custom properties. This can be - later changed by changing self.custom_properties. - - Raises: - DataTableException: Raised if the data and the description did not match, - or did not use the supported formats. - """ - self.__columns = self.TableDescriptionParser(table_description) - self.__data = [] - self.custom_properties = {} - if custom_properties is not None: - self.custom_properties = custom_properties - if data: - self.LoadData(data) - - @staticmethod - def CoerceValue(value, value_type): - """Coerces a single value into the type expected for its column. - - Internal helper method. - - Args: - value: The value which should be converted - value_type: One of "string", "number", "boolean", "date", "datetime" or - "timeofday". - - Returns: - An item of the Python type appropriate to the given value_type. Strings - are also converted to Unicode using UTF-8 encoding if necessary. - If a tuple is given, it should be in one of the following forms: - - (value, formatted value) - - (value, formatted value, custom properties) - where the formatted value is a string, and custom properties is a - dictionary of the custom properties for this cell. - To specify custom properties without specifying formatted value, one can - pass None as the formatted value. - One can also have a null-valued cell with formatted value and/or custom - properties by specifying None for the value. - This method ignores the custom properties except for checking that it is a - dictionary. The custom properties are handled in the ToJSon and ToJSCode - methods. - The real type of the given value is not strictly checked. For example, - any type can be used for string - as we simply take its str( ) and for - boolean value we just check "if value". - Examples: - CoerceValue(None, "string") returns None - CoerceValue((5, "5$"), "number") returns (5, "5$") - CoerceValue(100, "string") returns "100" - CoerceValue(0, "boolean") returns False - - Raises: - DataTableException: The value and type did not match in a not-recoverable - way, for example given value 'abc' for type 'number'. - """ - if isinstance(value, tuple): - # In case of a tuple, we run the same function on the value itself and - # add the formatted value. - if (len(value) not in [2, 3] or - (len(value) == 3 and not isinstance(value[2], dict))): - raise DataTableException("Wrong format for value and formatting - %s." % - str(value)) - if not isinstance(value[1], types.StringTypes + (types.NoneType,)): - raise DataTableException("Formatted value is not string, given %s." % - type(value[1])) - js_value = DataTable.CoerceValue(value[0], value_type) - return (js_value,) + value[1:] - - t_value = type(value) - if value is None: - return value - if value_type == "boolean": - return bool(value) - - elif value_type == "number": - if isinstance(value, (int, long, float)): - return value - raise DataTableException("Wrong type %s when expected number" % t_value) - - elif value_type == "string": - if isinstance(value, unicode): - return value - else: - return str(value).decode("utf-8") - - elif value_type == "date": - if isinstance(value, datetime.datetime): - return datetime.date(value.year, value.month, value.day) - elif isinstance(value, datetime.date): - return value - else: - raise DataTableException("Wrong type %s when expected date" % t_value) - - elif value_type == "timeofday": - if isinstance(value, datetime.datetime): - return datetime.time(value.hour, value.minute, value.second) - elif isinstance(value, datetime.time): - return value - else: - raise DataTableException("Wrong type %s when expected time" % t_value) - - elif value_type == "datetime": - if isinstance(value, datetime.datetime): - return value - else: - raise DataTableException("Wrong type %s when expected datetime" % - t_value) - # If we got here, it means the given value_type was not one of the - # supported types. - raise DataTableException("Unsupported type %s" % value_type) - - @staticmethod - def EscapeForJSCode(encoder, value): - if value is None: - return "null" - elif isinstance(value, datetime.datetime): - if value.microsecond == 0: - # If it's not ms-resolution, leave that out to save space. - return "new Date(%d,%d,%d,%d,%d,%d)" % (value.year, - value.month - 1, # To match JS - value.day, - value.hour, - value.minute, - value.second) - else: - return "new Date(%d,%d,%d,%d,%d,%d,%d)" % (value.year, - value.month - 1, # match JS - value.day, - value.hour, - value.minute, - value.second, - value.microsecond / 1000) - elif isinstance(value, datetime.date): - return "new Date(%d,%d,%d)" % (value.year, value.month - 1, value.day) - else: - return encoder.encode(value) - - @staticmethod - def ToString(value): - if value is None: - return "(empty)" - elif isinstance(value, (datetime.datetime, - datetime.date, - datetime.time)): - return str(value) - elif isinstance(value, unicode): - return value - elif isinstance(value, bool): - return str(value).lower() - else: - return str(value).decode("utf-8") - - @staticmethod - def ColumnTypeParser(description): - """Parses a single column description. Internal helper method. - - Args: - description: a column description in the possible formats: - 'id' - ('id',) - ('id', 'type') - ('id', 'type', 'label') - ('id', 'type', 'label', {'custom_prop1': 'custom_val1'}) - Returns: - Dictionary with the following keys: id, label, type, and - custom_properties where: - - If label not given, it equals the id. - - If type not given, string is used by default. - - If custom properties are not given, an empty dictionary is used by - default. - - Raises: - DataTableException: The column description did not match the RE, or - unsupported type was passed. - """ - if not description: - raise DataTableException("Description error: empty description given") - - if not isinstance(description, (types.StringTypes, tuple)): - raise DataTableException("Description error: expected either string or " - "tuple, got %s." % type(description)) - - if isinstance(description, types.StringTypes): - description = (description,) - - # According to the tuple's length, we fill the keys - # We verify everything is of type string - for elem in description[:3]: - if not isinstance(elem, types.StringTypes): - raise DataTableException("Description error: expected tuple of " - "strings, current element of type %s." % - type(elem)) - desc_dict = {"id": description[0], - "label": description[0], - "type": "string", - "custom_properties": {}} - if len(description) > 1: - desc_dict["type"] = description[1].lower() - if len(description) > 2: - desc_dict["label"] = description[2] - if len(description) > 3: - if not isinstance(description[3], dict): - raise DataTableException("Description error: expected custom " - "properties of type dict, current element " - "of type %s." % type(description[3])) - desc_dict["custom_properties"] = description[3] - if len(description) > 4: - raise DataTableException("Description error: tuple of length > 4") - if desc_dict["type"] not in ["string", "number", "boolean", - "date", "datetime", "timeofday"]: - raise DataTableException( - "Description error: unsupported type '%s'" % desc_dict["type"]) - return desc_dict - - @staticmethod - def TableDescriptionParser(table_description, depth=0): - """Parses the table_description object for internal use. - - Parses the user-submitted table description into an internal format used - by the Python DataTable class. Returns the flat list of parsed columns. - - Args: - table_description: A description of the table which should comply - with one of the formats described below. - depth: Optional. The depth of the first level in the current description. - Used by recursive calls to this function. - - Returns: - List of columns, where each column represented by a dictionary with the - keys: id, label, type, depth, container which means the following: - - id: the id of the column - - name: The name of the column - - type: The datatype of the elements in this column. Allowed types are - described in ColumnTypeParser(). - - depth: The depth of this column in the table description - - container: 'dict', 'iter' or 'scalar' for parsing the format easily. - - custom_properties: The custom properties for this column. - The returned description is flattened regardless of how it was given. - - Raises: - DataTableException: Error in a column description or in the description - structure. - - Examples: - A column description can be of the following forms: - 'id' - ('id',) - ('id', 'type') - ('id', 'type', 'label') - ('id', 'type', 'label', {'custom_prop1': 'custom_val1'}) - or as a dictionary: - 'id': 'type' - 'id': ('type',) - 'id': ('type', 'label') - 'id': ('type', 'label', {'custom_prop1': 'custom_val1'}) - If the type is not specified, we treat it as string. - If no specific label is given, the label is simply the id. - If no custom properties are given, we use an empty dictionary. - - input: [('a', 'date'), ('b', 'timeofday', 'b', {'foo': 'bar'})] - output: [{'id': 'a', 'label': 'a', 'type': 'date', - 'depth': 0, 'container': 'iter', 'custom_properties': {}}, - {'id': 'b', 'label': 'b', 'type': 'timeofday', - 'depth': 0, 'container': 'iter', - 'custom_properties': {'foo': 'bar'}}] - - input: {'a': [('b', 'number'), ('c', 'string', 'column c')]} - output: [{'id': 'a', 'label': 'a', 'type': 'string', - 'depth': 0, 'container': 'dict', 'custom_properties': {}}, - {'id': 'b', 'label': 'b', 'type': 'number', - 'depth': 1, 'container': 'iter', 'custom_properties': {}}, - {'id': 'c', 'label': 'column c', 'type': 'string', - 'depth': 1, 'container': 'iter', 'custom_properties': {}}] - - input: {('a', 'number', 'column a'): { 'b': 'number', 'c': 'string'}} - output: [{'id': 'a', 'label': 'column a', 'type': 'number', - 'depth': 0, 'container': 'dict', 'custom_properties': {}}, - {'id': 'b', 'label': 'b', 'type': 'number', - 'depth': 1, 'container': 'dict', 'custom_properties': {}}, - {'id': 'c', 'label': 'c', 'type': 'string', - 'depth': 1, 'container': 'dict', 'custom_properties': {}}] - - input: { ('w', 'string', 'word'): ('c', 'number', 'count') } - output: [{'id': 'w', 'label': 'word', 'type': 'string', - 'depth': 0, 'container': 'dict', 'custom_properties': {}}, - {'id': 'c', 'label': 'count', 'type': 'number', - 'depth': 1, 'container': 'scalar', 'custom_properties': {}}] - - input: {'a': ('number', 'column a'), 'b': ('string', 'column b')} - output: [{'id': 'a', 'label': 'column a', 'type': 'number', 'depth': 0, - 'container': 'dict', 'custom_properties': {}}, - {'id': 'b', 'label': 'column b', 'type': 'string', 'depth': 0, - 'container': 'dict', 'custom_properties': {}} - - NOTE: there might be ambiguity in the case of a dictionary representation - of a single column. For example, the following description can be parsed - in 2 different ways: {'a': ('b', 'c')} can be thought of a single column - with the id 'a', of type 'b' and the label 'c', or as 2 columns: one named - 'a', and the other named 'b' of type 'c'. We choose the first option by - default, and in case the second option is the right one, it is possible to - make the key into a tuple (i.e. {('a',): ('b', 'c')}) or add more info - into the tuple, thus making it look like this: {'a': ('b', 'c', 'b', {})} - -- second 'b' is the label, and {} is the custom properties field. - """ - # For the recursion step, we check for a scalar object (string or tuple) - if isinstance(table_description, (types.StringTypes, tuple)): - parsed_col = DataTable.ColumnTypeParser(table_description) - parsed_col["depth"] = depth - parsed_col["container"] = "scalar" - return [parsed_col] - - # Since it is not scalar, table_description must be iterable. - if not hasattr(table_description, "__iter__"): - raise DataTableException("Expected an iterable object, got %s" % - type(table_description)) - if not isinstance(table_description, dict): - # We expects a non-dictionary iterable item. - columns = [] - for desc in table_description: - parsed_col = DataTable.ColumnTypeParser(desc) - parsed_col["depth"] = depth - parsed_col["container"] = "iter" - columns.append(parsed_col) - if not columns: - raise DataTableException("Description iterable objects should not" - " be empty.") - return columns - # The other case is a dictionary - if not table_description: - raise DataTableException("Empty dictionaries are not allowed inside" - " description") - - # To differentiate between the two cases of more levels below or this is - # the most inner dictionary, we consider the number of keys (more then one - # key is indication for most inner dictionary) and the type of the key and - # value in case of only 1 key (if the type of key is string and the type of - # the value is a tuple of 0-3 items, we assume this is the most inner - # dictionary). - # NOTE: this way of differentiating might create ambiguity. See docs. - if (len(table_description) != 1 or - (isinstance(table_description.keys()[0], types.StringTypes) and - isinstance(table_description.values()[0], tuple) and - len(table_description.values()[0]) < 4)): - # This is the most inner dictionary. Parsing types. - columns = [] - # We sort the items, equivalent to sort the keys since they are unique - for key, value in sorted(table_description.items()): - # We parse the column type as (key, type) or (key, type, label) using - # ColumnTypeParser. - if isinstance(value, tuple): - parsed_col = DataTable.ColumnTypeParser((key,) + value) - else: - parsed_col = DataTable.ColumnTypeParser((key, value)) - parsed_col["depth"] = depth - parsed_col["container"] = "dict" - columns.append(parsed_col) - return columns - # This is an outer dictionary, must have at most one key. - parsed_col = DataTable.ColumnTypeParser(table_description.keys()[0]) - parsed_col["depth"] = depth - parsed_col["container"] = "dict" - return ([parsed_col] + - DataTable.TableDescriptionParser(table_description.values()[0], - depth=depth + 1)) - - @property - def columns(self): - """Returns the parsed table description.""" - return self.__columns - - def NumberOfRows(self): - """Returns the number of rows in the current data stored in the table.""" - return len(self.__data) - - def SetRowsCustomProperties(self, rows, custom_properties): - """Sets the custom properties for given row(s). - - Can accept a single row or an iterable of rows. - Sets the given custom properties for all specified rows. - - Args: - rows: The row, or rows, to set the custom properties for. - custom_properties: A string to string dictionary of custom properties to - set for all rows. - """ - if not hasattr(rows, "__iter__"): - rows = [rows] - for row in rows: - self.__data[row] = (self.__data[row][0], custom_properties) - - def LoadData(self, data, custom_properties=None): - """Loads new rows to the data table, clearing existing rows. - - May also set the custom_properties for the added rows. The given custom - properties dictionary specifies the dictionary that will be used for *all* - given rows. - - Args: - data: The rows that the table will contain. - custom_properties: A dictionary of string to string to set as the custom - properties for all rows. - """ - self.__data = [] - self.AppendData(data, custom_properties) - - def AppendData(self, data, custom_properties=None): - """Appends new data to the table. - - Data is appended in rows. Data must comply with - the table schema passed in to __init__(). See CoerceValue() for a list - of acceptable data types. See the class documentation for more information - and examples of schema and data values. - - Args: - data: The row to add to the table. The data must conform to the table - description format. - custom_properties: A dictionary of string to string, representing the - custom properties to add to all the rows. - - Raises: - DataTableException: The data structure does not match the description. - """ - # If the maximal depth is 0, we simply iterate over the data table - # lines and insert them using _InnerAppendData. Otherwise, we simply - # let the _InnerAppendData handle all the levels. - if not self.__columns[-1]["depth"]: - for row in data: - self._InnerAppendData(({}, custom_properties), row, 0) - else: - self._InnerAppendData(({}, custom_properties), data, 0) - - def _InnerAppendData(self, prev_col_values, data, col_index): - """Inner function to assist LoadData.""" - # We first check that col_index has not exceeded the columns size - if col_index >= len(self.__columns): - raise DataTableException("The data does not match description, too deep") - - # Dealing with the scalar case, the data is the last value. - if self.__columns[col_index]["container"] == "scalar": - prev_col_values[0][self.__columns[col_index]["id"]] = data - self.__data.append(prev_col_values) - return - - if self.__columns[col_index]["container"] == "iter": - if not hasattr(data, "__iter__") or isinstance(data, dict): - raise DataTableException("Expected iterable object, got %s" % - type(data)) - # We only need to insert the rest of the columns - # If there are less items than expected, we only add what there is. - for value in data: - if col_index >= len(self.__columns): - raise DataTableException("Too many elements given in data") - prev_col_values[0][self.__columns[col_index]["id"]] = value - col_index += 1 - self.__data.append(prev_col_values) - return - - # We know the current level is a dictionary, we verify the type. - if not isinstance(data, dict): - raise DataTableException("Expected dictionary at current level, got %s" % - type(data)) - # We check if this is the last level - if self.__columns[col_index]["depth"] == self.__columns[-1]["depth"]: - # We need to add the keys in the dictionary as they are - for col in self.__columns[col_index:]: - if col["id"] in data: - prev_col_values[0][col["id"]] = data[col["id"]] - self.__data.append(prev_col_values) - return - - # We have a dictionary in an inner depth level. - if not data.keys(): - # In case this is an empty dictionary, we add a record with the columns - # filled only until this point. - self.__data.append(prev_col_values) - else: - for key in sorted(data): - col_values = dict(prev_col_values[0]) - col_values[self.__columns[col_index]["id"]] = key - self._InnerAppendData((col_values, prev_col_values[1]), - data[key], col_index + 1) - - def _PreparedData(self, order_by=()): - """Prepares the data for enumeration - sorting it by order_by. - - Args: - order_by: Optional. Specifies the name of the column(s) to sort by, and - (optionally) which direction to sort in. Default sort direction - is asc. Following formats are accepted: - "string_col_name" -- For a single key in default (asc) order. - ("string_col_name", "asc|desc") -- For a single key. - [("col_1","asc|desc"), ("col_2","asc|desc")] -- For more than - one column, an array of tuples of (col_name, "asc|desc"). - - Returns: - The data sorted by the keys given. - - Raises: - DataTableException: Sort direction not in 'asc' or 'desc' - """ - if not order_by: - return self.__data - - proper_sort_keys = [] - if isinstance(order_by, types.StringTypes) or ( - isinstance(order_by, tuple) and len(order_by) == 2 and - order_by[1].lower() in ["asc", "desc"]): - order_by = (order_by,) - for key in order_by: - if isinstance(key, types.StringTypes): - proper_sort_keys.append((key, 1)) - elif (isinstance(key, (list, tuple)) and len(key) == 2 and - key[1].lower() in ("asc", "desc")): - proper_sort_keys.append((key[0], key[1].lower() == "asc" and 1 or -1)) - else: - raise DataTableException("Expected tuple with second value: " - "'asc' or 'desc'") - - def SortCmpFunc(row1, row2): - """cmp function for sorted. Compares by keys and 'asc'/'desc' keywords.""" - for key, asc_mult in proper_sort_keys: - cmp_result = asc_mult * cmp(row1[0].get(key), row2[0].get(key)) - if cmp_result: - return cmp_result - return 0 - - return sorted(self.__data, cmp=SortCmpFunc) - - def ToJSCode(self, name, columns_order=None, order_by=()): - """Writes the data table as a JS code string. - - This method writes a string of JS code that can be run to - generate a DataTable with the specified data. Typically used for debugging - only. - - Args: - name: The name of the table. The name would be used as the DataTable's - variable name in the created JS code. - columns_order: Optional. Specifies the order of columns in the - output table. Specify a list of all column IDs in the order - in which you want the table created. - Note that you must list all column IDs in this parameter, - if you use it. - order_by: Optional. Specifies the name of the column(s) to sort by. - Passed as is to _PreparedData. - - Returns: - A string of JS code that, when run, generates a DataTable with the given - name and the data stored in the DataTable object. - Example result: - "var tab1 = new google.visualization.DataTable(); - tab1.addColumn("string", "a", "a"); - tab1.addColumn("number", "b", "b"); - tab1.addColumn("boolean", "c", "c"); - tab1.addRows(10); - tab1.setCell(0, 0, "a"); - tab1.setCell(0, 1, 1, null, {"foo": "bar"}); - tab1.setCell(0, 2, true); - ... - tab1.setCell(9, 0, "c"); - tab1.setCell(9, 1, 3, "3$"); - tab1.setCell(9, 2, false);" - - Raises: - DataTableException: The data does not match the type. - """ - - encoder = DataTableJSONEncoder() - - if columns_order is None: - columns_order = [col["id"] for col in self.__columns] - col_dict = dict([(col["id"], col) for col in self.__columns]) - - # We first create the table with the given name - jscode = "var %s = new google.visualization.DataTable();\n" % name - if self.custom_properties: - jscode += "%s.setTableProperties(%s);\n" % ( - name, encoder.encode(self.custom_properties)) - - # We add the columns to the table - for i, col in enumerate(columns_order): - jscode += "%s.addColumn(%s, %s, %s);\n" % ( - name, - encoder.encode(col_dict[col]["type"]), - encoder.encode(col_dict[col]["label"]), - encoder.encode(col_dict[col]["id"])) - if col_dict[col]["custom_properties"]: - jscode += "%s.setColumnProperties(%d, %s);\n" % ( - name, i, encoder.encode(col_dict[col]["custom_properties"])) - jscode += "%s.addRows(%d);\n" % (name, len(self.__data)) - - # We now go over the data and add each row - for (i, (row, cp)) in enumerate(self._PreparedData(order_by)): - # We add all the elements of this row by their order - for (j, col) in enumerate(columns_order): - if col not in row or row[col] is None: - continue - value = self.CoerceValue(row[col], col_dict[col]["type"]) - if isinstance(value, tuple): - cell_cp = "" - if len(value) == 3: - cell_cp = ", %s" % encoder.encode(row[col][2]) - # We have a formatted value or custom property as well - jscode += ("%s.setCell(%d, %d, %s, %s%s);\n" % - (name, i, j, - self.EscapeForJSCode(encoder, value[0]), - self.EscapeForJSCode(encoder, value[1]), cell_cp)) - else: - jscode += "%s.setCell(%d, %d, %s);\n" % ( - name, i, j, self.EscapeForJSCode(encoder, value)) - if cp: - jscode += "%s.setRowProperties(%d, %s);\n" % ( - name, i, encoder.encode(cp)) - return jscode - - def ToHtml(self, columns_order=None, order_by=()): - """Writes the data table as an HTML table code string. - - Args: - columns_order: Optional. Specifies the order of columns in the - output table. Specify a list of all column IDs in the order - in which you want the table created. - Note that you must list all column IDs in this parameter, - if you use it. - order_by: Optional. Specifies the name of the column(s) to sort by. - Passed as is to _PreparedData. - - Returns: - An HTML table code string. - Example result (the result is without the newlines): - <html><body><table border="1"> - <thead><tr><th>a</th><th>b</th><th>c</th></tr></thead> - <tbody> - <tr><td>1</td><td>"z"</td><td>2</td></tr> - <tr><td>"3$"</td><td>"w"</td><td></td></tr> - </tbody> - </table></body></html> - - Raises: - DataTableException: The data does not match the type. - """ - table_template = "<html><body><table border=\"1\">%s</table></body></html>" - columns_template = "<thead><tr>%s</tr></thead>" - rows_template = "<tbody>%s</tbody>" - row_template = "<tr>%s</tr>" - header_cell_template = "<th>%s</th>" - cell_template = "<td>%s</td>" - - if columns_order is None: - columns_order = [col["id"] for col in self.__columns] - col_dict = dict([(col["id"], col) for col in self.__columns]) - - columns_list = [] - for col in columns_order: - columns_list.append(header_cell_template % - cgi.escape(col_dict[col]["label"])) - columns_html = columns_template % "".join(columns_list) - - rows_list = [] - # We now go over the data and add each row - for row, unused_cp in self._PreparedData(order_by): - cells_list = [] - # We add all the elements of this row by their order - for col in columns_order: - # For empty string we want empty quotes (""). - value = "" - if col in row and row[col] is not None: - value = self.CoerceValue(row[col], col_dict[col]["type"]) - if isinstance(value, tuple): - # We have a formatted value and we're going to use it - cells_list.append(cell_template % cgi.escape(self.ToString(value[1]))) - else: - cells_list.append(cell_template % cgi.escape(self.ToString(value))) - rows_list.append(row_template % "".join(cells_list)) - rows_html = rows_template % "".join(rows_list) - - return table_template % (columns_html + rows_html) - - def ToCsv(self, columns_order=None, order_by=(), separator=","): - """Writes the data table as a CSV string. - - Output is encoded in UTF-8 because the Python "csv" module can't handle - Unicode properly according to its documentation. - - Args: - columns_order: Optional. Specifies the order of columns in the - output table. Specify a list of all column IDs in the order - in which you want the table created. - Note that you must list all column IDs in this parameter, - if you use it. - order_by: Optional. Specifies the name of the column(s) to sort by. - Passed as is to _PreparedData. - separator: Optional. The separator to use between the values. - - Returns: - A CSV string representing the table. - Example result: - 'a','b','c' - 1,'z',2 - 3,'w','' - - Raises: - DataTableException: The data does not match the type. - """ - - csv_buffer = cStringIO.StringIO() - writer = csv.writer(csv_buffer, delimiter=separator) - - if columns_order is None: - columns_order = [col["id"] for col in self.__columns] - col_dict = dict([(col["id"], col) for col in self.__columns]) - - writer.writerow([col_dict[col]["label"].encode("utf-8") - for col in columns_order]) - - # We now go over the data and add each row - for row, unused_cp in self._PreparedData(order_by): - cells_list = [] - # We add all the elements of this row by their order - for col in columns_order: - value = "" - if col in row and row[col] is not None: - value = self.CoerceValue(row[col], col_dict[col]["type"]) - if isinstance(value, tuple): - # We have a formatted value. Using it only for date/time types. - if col_dict[col]["type"] in ["date", "datetime", "timeofday"]: - cells_list.append(self.ToString(value[1]).encode("utf-8")) - else: - cells_list.append(self.ToString(value[0]).encode("utf-8")) - else: - cells_list.append(self.ToString(value).encode("utf-8")) - writer.writerow(cells_list) - return csv_buffer.getvalue() - - def ToTsvExcel(self, columns_order=None, order_by=()): - """Returns a file in tab-separated-format readable by MS Excel. - - Returns a file in UTF-16 little endian encoding, with tabs separating the - values. - - Args: - columns_order: Delegated to ToCsv. - order_by: Delegated to ToCsv. - - Returns: - A tab-separated little endian UTF16 file representing the table. - """ - return (self.ToCsv(columns_order, order_by, separator="\t") - .decode("utf-8").encode("UTF-16LE")) - - def _ToJSonObj(self, columns_order=None, order_by=()): - """Returns an object suitable to be converted to JSON. - - Args: - columns_order: Optional. A list of all column IDs in the order in which - you want them created in the output table. If specified, - all column IDs must be present. - order_by: Optional. Specifies the name of the column(s) to sort by. - Passed as is to _PreparedData(). - - Returns: - A dictionary object for use by ToJSon or ToJSonResponse. - """ - if columns_order is None: - columns_order = [col["id"] for col in self.__columns] - col_dict = dict([(col["id"], col) for col in self.__columns]) - - # Creating the column JSON objects - col_objs = [] - for col_id in columns_order: - col_obj = {"id": col_dict[col_id]["id"], - "label": col_dict[col_id]["label"], - "type": col_dict[col_id]["type"]} - if col_dict[col_id]["custom_properties"]: - col_obj["p"] = col_dict[col_id]["custom_properties"] - col_objs.append(col_obj) - - # Creating the rows jsons - row_objs = [] - for row, cp in self._PreparedData(order_by): - cell_objs = [] - for col in columns_order: - value = self.CoerceValue(row.get(col, None), col_dict[col]["type"]) - if value is None: - cell_obj = None - elif isinstance(value, tuple): - cell_obj = {"v": value[0]} - if len(value) > 1 and value[1] is not None: - cell_obj["f"] = value[1] - if len(value) == 3: - cell_obj["p"] = value[2] - else: - cell_obj = {"v": value} - cell_objs.append(cell_obj) - row_obj = {"c": cell_objs} - if cp: - row_obj["p"] = cp - row_objs.append(row_obj) - - json_obj = {"cols": col_objs, "rows": row_objs} - if self.custom_properties: - json_obj["p"] = self.custom_properties - - return json_obj - - def ToJSon(self, columns_order=None, order_by=()): - """Returns a string that can be used in a JS DataTable constructor. - - This method writes a JSON string that can be passed directly into a Google - Visualization API DataTable constructor. Use this output if you are - hosting the visualization HTML on your site, and want to code the data - table in Python. Pass this string into the - google.visualization.DataTable constructor, e.g,: - ... on my page that hosts my visualization ... - google.setOnLoadCallback(drawTable); - function drawTable() { - var data = new google.visualization.DataTable(_my_JSon_string, 0.6); - myTable.draw(data); - } - - Args: - columns_order: Optional. Specifies the order of columns in the - output table. Specify a list of all column IDs in the order - in which you want the table created. - Note that you must list all column IDs in this parameter, - if you use it. - order_by: Optional. Specifies the name of the column(s) to sort by. - Passed as is to _PreparedData(). - - Returns: - A JSon constructor string to generate a JS DataTable with the data - stored in the DataTable object. - Example result (the result is without the newlines): - {cols: [{id:"a",label:"a",type:"number"}, - {id:"b",label:"b",type:"string"}, - {id:"c",label:"c",type:"number"}], - rows: [{c:[{v:1},{v:"z"},{v:2}]}, c:{[{v:3,f:"3$"},{v:"w"},{v:null}]}], - p: {'foo': 'bar'}} - - Raises: - DataTableException: The data does not match the type. - """ - - encoder = DataTableJSONEncoder() - return encoder.encode( - self._ToJSonObj(columns_order, order_by)).encode("utf-8") - - def ToJSonResponse(self, columns_order=None, order_by=(), req_id=0, - response_handler="google.visualization.Query.setResponse"): - """Writes a table as a JSON response that can be returned as-is to a client. - - This method writes a JSON response to return to a client in response to a - Google Visualization API query. This string can be processed by the calling - page, and is used to deliver a data table to a visualization hosted on - a different page. - - Args: - columns_order: Optional. Passed straight to self.ToJSon(). - order_by: Optional. Passed straight to self.ToJSon(). - req_id: Optional. The response id, as retrieved by the request. - response_handler: Optional. The response handler, as retrieved by the - request. - - Returns: - A JSON response string to be received by JS the visualization Query - object. This response would be translated into a DataTable on the - client side. - Example result (newlines added for readability): - google.visualization.Query.setResponse({ - 'version':'0.6', 'reqId':'0', 'status':'OK', - 'table': {cols: [...], rows: [...]}}); - - Note: The URL returning this string can be used as a data source by Google - Visualization Gadgets or from JS code. - """ - - response_obj = { - "version": "0.6", - "reqId": str(req_id), - "table": self._ToJSonObj(columns_order, order_by), - "status": "ok" - } - encoder = DataTableJSONEncoder() - return "%s(%s);" % (response_handler, - encoder.encode(response_obj).encode("utf-8")) - - def ToResponse(self, columns_order=None, order_by=(), tqx=""): - """Writes the right response according to the request string passed in tqx. - - This method parses the tqx request string (format of which is defined in - the documentation for implementing a data source of Google Visualization), - and returns the right response according to the request. - It parses out the "out" parameter of tqx, calls the relevant response - (ToJSonResponse() for "json", ToCsv() for "csv", ToHtml() for "html", - ToTsvExcel() for "tsv-excel") and passes the response function the rest of - the relevant request keys. - - Args: - columns_order: Optional. Passed as is to the relevant response function. - order_by: Optional. Passed as is to the relevant response function. - tqx: Optional. The request string as received by HTTP GET. Should be in - the format "key1:value1;key2:value2...". All keys have a default - value, so an empty string will just do the default (which is calling - ToJSonResponse() with no extra parameters). - - Returns: - A response string, as returned by the relevant response function. - - Raises: - DataTableException: One of the parameters passed in tqx is not supported. - """ - tqx_dict = {} - if tqx: - tqx_dict = dict(opt.split(":") for opt in tqx.split(";")) - if tqx_dict.get("version", "0.6") != "0.6": - raise DataTableException( - "Version (%s) passed by request is not supported." - % tqx_dict["version"]) - - if tqx_dict.get("out", "json") == "json": - response_handler = tqx_dict.get("responseHandler", - "google.visualization.Query.setResponse") - return self.ToJSonResponse(columns_order, order_by, - req_id=tqx_dict.get("reqId", 0), - response_handler=response_handler) - elif tqx_dict["out"] == "html": - return self.ToHtml(columns_order, order_by) - elif tqx_dict["out"] == "csv": - return self.ToCsv(columns_order, order_by) - elif tqx_dict["out"] == "tsv-excel": - return self.ToTsvExcel(columns_order, order_by) - else: - raise DataTableException( - "'out' parameter: '%s' is not supported" % tqx_dict["out"]) |