Commit fedcde2099e1ae08667b00fd583155700b09ae3b

Authored by Philippe Lagadec
1 parent 02bebc79

added prettytable, used in oleid and oletimes to improve output

oletools/oleid.py
... ... @@ -47,8 +47,9 @@ http://www.decalage.info/python/oletools
47 47 # 2012-10-29 v0.01 PL: - first version
48 48 # 2014-11-29 v0.02 PL: - use olefile instead of OleFileIO_PL
49 49 # - improved usage display with -h
  50 +# 2014-11-30 v0.03 PL: - improved output with prettytable
50 51  
51   -__version__ = '0.02'
  52 +__version__ = '0.03'
52 53  
53 54  
54 55 #------------------------------------------------------------------------------
... ... @@ -71,6 +72,7 @@ __version__ = '0.02'
71 72  
72 73 import optparse, sys, os, re, zlib, struct
73 74 import thirdparty.olefile as olefile
  75 +from thirdparty.prettytable import prettytable
74 76  
75 77  
76 78 #=== FUNCTIONS ===============================================================
... ... @@ -276,8 +278,17 @@ def main():
276 278 print '\nFilename:', filename
277 279 oleid = OleID(filename)
278 280 indicators = oleid.check()
  281 +
  282 + t = prettytable.PrettyTable(['Indicator', 'Value'])
  283 + t.align = 'l'
  284 + t.max_width = 39
  285 + #t.border = False
  286 +
279 287 for indicator in indicators:
280   - print '%s: %s' % (indicator.name, indicator.value)
  288 + #print '%s: %s' % (indicator.name, indicator.value)
  289 + t.add_row((indicator.name, indicator.value))
  290 +
  291 + print t
281 292  
282 293 if __name__ == '__main__':
283 294 main()
... ...
oletools/oletimes.py
... ... @@ -45,8 +45,9 @@ http://www.decalage.info/python/oletools
45 45 # 2013-07-24 v0.01 PL: - first version
46 46 # 2014-11-29 v0.02 PL: - use olefile instead of OleFileIO_PL
47 47 # - improved usage display
  48 +# 2014-11-30 v0.03 PL: - improved output with prettytable
48 49  
49   -__version__ = '0.02'
  50 +__version__ = '0.03'
50 51  
51 52 #------------------------------------------------------------------------------
52 53 # TODO:
... ... @@ -57,8 +58,9 @@ __version__ = '0.02'
57 58  
58 59 #=== IMPORTS =================================================================
59 60  
60   -import sys
  61 +import sys, datetime
61 62 import thirdparty.olefile as olefile
  63 +from thirdparty.prettytable import prettytable
62 64  
63 65  
64 66 #=== MAIN =================================================================
... ... @@ -68,9 +70,30 @@ try:
68 70 except IndexError:
69 71 sys.exit(__doc__)
70 72  
71   -print'- Root mtime=%s ctime=%s' % (ole.root.getmtime(), ole.root.getctime())
  73 +def dt2str (dt):
  74 + """
  75 + Convert a datetime object to a string for display, without microseconds
  76 +
  77 + :param dt: datetime.datetime object, or None
  78 + :return: str, or None
  79 + """
  80 + if dt is None:
  81 + return None
  82 + dt = dt.replace(microsecond = 0)
  83 + return str(dt)
  84 +
  85 +t = prettytable.PrettyTable(['Stream/Storage name', 'Modification Time', 'Creation Time'])
  86 +t.align = 'l'
  87 +t.max_width = 26
  88 +#t.border = False
  89 +
  90 +#print'- Root mtime=%s ctime=%s' % (ole.root.getmtime(), ole.root.getctime())
  91 +t.add_row(('Root', dt2str(ole.root.getmtime()), dt2str(ole.root.getctime())))
72 92  
73 93 for obj in ole.listdir(streams=True, storages=True):
74   - print '- %s: mtime=%s ctime=%s' % (repr('/'.join(obj)), ole.getmtime(obj), ole.getctime(obj))
  94 + #print '- %s: mtime=%s ctime=%s' % (repr('/'.join(obj)), ole.getmtime(obj), ole.getctime(obj))
  95 + t.add_row((repr('/'.join(obj)), dt2str(ole.getmtime(obj)), dt2str(ole.getctime(obj))))
  96 +
  97 +print t
75 98  
76 99 ole.close()
... ...
oletools/thirdparty/prettytable/CHANGELOG 0 → 100644
  1 +########## PrettyTable 0.7 - Feb 17, 2013 ###########
  2 +
  3 +* Improved Python 2 and 3 compatibility (2.4-3.2).
  4 +* Improved support for non-Latin characters. Table widths should
  5 + now be calculated correctly for tables with e.g. Japanese text.
  6 +* Table contents can now be read in from a .csv file
  7 +* Table contents can now be read in from a DB-API compatible cursor
  8 +* Table contents can now be read in from a string containing a
  9 + HTML table (thanks to Christoph Robbert for submitting this patch!)
  10 +* new valign attribute controls vertical alignment of text when
  11 + some cells in a row have multiple lines of text and others don't.
  12 + (thanks to Google Code user maartendb for submitting this patch!)
  13 +* hrules attribute can now be set to HEADER, which draws a rule only
  14 + under the header row
  15 +* new vrules attribute controls drawing of vertical rules and can
  16 + be set to FRAME, ALL or NONE
  17 +* new header_style attribute controls formatting of text in table
  18 + headers and can be set to "cap", "title", "upper", "lower" or None
  19 +* Fixed a simple bug regarding validation of max_width (thanks to
  20 + Anthony Toole for pointing out this bug and providing a patch).
  21 +* Fixed a simple bug regarding initialisation of int_format value
  22 + for new tables (thanks to Ingo Schmiegel for pointing out this
  23 + bug!)
  24 +* Fixed a bug regarding some constructor keywords, such as "border",
  25 + being ignored (thanks to Google Code user antonio.s.messina for
  26 + reporting this bug).
  27 +
  28 +########## PrettyTable 0.6 - May 5, 2012 ##########
  29 +
  30 +* Code is now simultaneously compatible with Python 2 and 3
  31 +* Replaced all setter methods with managed attributes
  32 +* All styling options can now be set persistently as managed attributes
  33 +* Added "add_style" method to make setting style options easily
  34 +* Added "del_row", "clear_rows" and "clear" methods to facilitate
  35 + removal of data from table.
  36 +* Added "copy" method to facilitate cloning of a table.
  37 +* Removed caching functionality, which added complexity and fragility
  38 + for relatively little gain
  39 +* Removed methods that just printed strings produced by get_string and
  40 + get_html_string - just use inbuilt print!
  41 +* Improved unicode support (thanks to Google Code user ru.w31rd0 for
  42 + patch!)
  43 +* Added support for decimal and floating point number formatting
  44 + support (thanks to Google Code user willfurnass for the suggestion!)
  45 +* Added support for using a custom key sorting methods (thanks to
  46 + Google Code user amannijhawan for the suggestion!)
  47 +* Added support for line breaks in data (suggested and implemented by
  48 + Klein Stephane)
  49 +* Added support for max column widths (thanks to Tibor Arpas for the
  50 + suggestion!)
  51 +* Fixed table slicing
  52 +* Fixed bug where closing <tr/> tags in HTML tables were not printed
  53 + (thanks to Google Code user kehander for reporting this bug!)
  54 +* Fixed HTML table sorting bug (thanks to Google Code user dougbeal
  55 + for reporting this bug!)
  56 +* Fixed bug whereby changing field_names did not recompute widths
  57 + (thanks to Google Code user denilsonsa for reporting this bug!)
  58 +
  59 +########## PrettyTable 0.5 - May 26, 2009 ##########
  60 +
  61 +* Fixed a bug whereby printing with headers=False and border=False
  62 + would introduce an extraneous newline. Thanks to Alexander Lamaison
  63 + for reporting this bug.
  64 +* When printing with headers=False, column widths will now be reduced
  65 + as appropriate in columns where the field name is wider than the
  66 + data. Thanks to Alexander Lamaison for suggesting this behaviour.
  67 +* Support for Unicode has improved. Thanks to Chris Clark for
  68 + submitting this improvement.
  69 +* The value of the "border" argument now correctly controls the
  70 + presence of a border when printing HTML tables with print_html or
  71 + get_html_string, instead of being incorrectly ignored. Thanks to
  72 + Chris Clark for fixing this.
  73 +* The print_html and get_html_string methods now accept an
  74 + "attributes" argument which is a dictionary of name/value pairs to be
  75 + placed inside the <table> tag (so you can, e.g. set class, name or id
  76 + values in order to style your table with CSS). Thanks to Chris Clark
  77 + for submitting this feature.
  78 +* The print_html and get_html_string methods now, by default, do their
  79 + best to match the various formatting options in their HTML output.
  80 + They use inline CSS to adjust the alignment of data in columns, the
  81 + padding widths of columns and in some cases the border settings. You
  82 + can give either method a "format=False" attribute to turn this
  83 + behaviour off if you want to do your own styling. With "format=False"
  84 + the methods print a "bare bones" table, similar to the default
  85 + behaviour in 0.4.
  86 +
  87 +########## PrettyTable 0.4 - May 13, 2009 ##########
  88 +
  89 +* Added "add_column" method to enable building tables up column-by-column.
  90 +* Added "print_HTML" and "get_HTML_string" methods to enable HTML table
  91 + production.
  92 +* Added "set_border_chars" method to enable control over characters used to
  93 + draw the table border.
  94 +* Added "set_left_padding" and "set_right_padding" methods to allow
  95 + independent padding control for both sides of a column.
  96 +* Added "sortby" option to enable column sorting.
  97 +* Added "header" option to enable switching off field name printing at top of
  98 + table.
  99 +* Modified "hrules" option to enable greater control over presence of
  100 + horizontal lines.
  101 +* Added "border" option to enable switching off all line printing.
  102 +
  103 +Thanks to Tim Cera, Chris Clark, Alexander Lamaison for suggesting and helping
  104 +to test many of the new features in this release.
  105 +
  106 +########## PrettyTable 0.3 - May 01, 2009 ##########
  107 +
  108 +* Added "padding_width" option to control the number of spaces between the
  109 + vertical line rules at the edges of a column and its content. This can be
  110 + set as a keyword argument to the constructor or after instantiation using
  111 + the "set_padding_width" method. The value is set to 1 by defaut. If your
  112 + table is too wide for a small screen with this value, setting it to 0 might
  113 + help you squeeze it in.
  114 +
  115 +Thanks to Chris Clark for contributing a patch against 0.2.1 to add this
  116 +feature!
  117 +
  118 +########## PrettyTable 0.2.1 - April 29, 2009 ##########
  119 +
  120 +* Caching no longer breaks when using the "printt(fields=[...])" syntax. The
  121 + list of fields was not hashable and hence could not be used as a dictionary
  122 + key. I fixed this using the output of the "cPickle" module's "dumps"
  123 + function as the dictionary key instead.
  124 +* Horizontal lines are now the appropriate length when the above syntax is
  125 + used.
  126 +
  127 +Thanks to Julien Koesten for reporting these bugs and testing the fixes almost
  128 +immediately after the release of 0.2!
  129 +
  130 +########## PrettyTable 0.2 - April 29, 2009 ##########
  131 +
  132 +* Added "get_string" method.
  133 +* Added "__str__" method (which just calls "get_string") to enable nice
  134 + "print x" syntax.
  135 +* Can now pass field names as a constructor argument.
  136 +* Return values of "get_string" are cached in a dictionary that is only
  137 + cleared after a call to "add_row" or something else which invalidates the
  138 + cache.
  139 +
  140 +########## PrettyTable 0.1 - February 26, 2009 #########
  141 +
  142 +* Original release
... ...
oletools/thirdparty/prettytable/COPYING 0 → 100644
  1 +# Copyright (c) 2009-2013 Luke Maurits <luke@maurits.id.au>
  2 +# All rights reserved.
  3 +# With contributions from:
  4 +# * Chris Clark
  5 +# * Christoph Robbert
  6 +# * Klein Stephane
  7 +# * "maartendb"
  8 +#
  9 +# Redistribution and use in source and binary forms, with or without
  10 +# modification, are permitted provided that the following conditions are met:
  11 +#
  12 +# * Redistributions of source code must retain the above copyright notice,
  13 +# this list of conditions and the following disclaimer.
  14 +# * Redistributions in binary form must reproduce the above copyright notice,
  15 +# this list of conditions and the following disclaimer in the documentation
  16 +# and/or other materials provided with the distribution.
  17 +# * The name of the author may not be used to endorse or promote products
  18 +# derived from this software without specific prior written permission.
  19 +#
  20 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21 +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22 +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23 +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  24 +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25 +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26 +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27 +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28 +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29 +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30 +# POSSIBILITY OF SUCH DAMAGE.
... ...
oletools/thirdparty/prettytable/README 0 → 100644
  1 +TUTORIAL ON HOW TO USE THE PRETTYTABLE 0.6+ API
  2 +
  3 +*** This tutorial is distributed with PrettyTable and is meant to serve
  4 +as a "quick start" guide for the lazy or impatient. It is not an
  5 +exhaustive description of the whole API, and it is not guaranteed to be
  6 +100% up to date. For more complete and update documentation, check the
  7 +PrettyTable wiki at http://code.google.com/p/prettytable/w/list ***
  8 +
  9 += Getting your data into (and out of) the table =
  10 +
  11 +Let's suppose you have a shiny new PrettyTable:
  12 +
  13 +from prettytable import PrettyTable
  14 +x = PrettyTable()
  15 +
  16 +and you want to put some data into it. You have a few options.
  17 +
  18 +== Row by row ==
  19 +
  20 +You can add data one row at a time. To do this you can set the field names
  21 +first using the `field_names` attribute, and then add the rows one at a time
  22 +using the `add_row` method:
  23 +
  24 +x.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  25 +x.add_row(["Adelaide",1295, 1158259, 600.5])
  26 +x.add_row(["Brisbane",5905, 1857594, 1146.4])
  27 +x.add_row(["Darwin", 112, 120900, 1714.7])
  28 +x.add_row(["Hobart", 1357, 205556, 619.5])
  29 +x.add_row(["Sydney", 2058, 4336374, 1214.8])
  30 +x.add_row(["Melbourne", 1566, 3806092, 646.9])
  31 +x.add_row(["Perth", 5386, 1554769, 869.4])
  32 +
  33 +== Column by column ==
  34 +
  35 +You can add data one column at a time as well. To do this you use the
  36 +`add_column` method, which takes two arguments - a string which is the name for
  37 +the field the column you are adding corresponds to, and a list or tuple which
  38 +contains the column data"
  39 +
  40 +x.add_column("City name",
  41 +["Adelaide","Brisbane","Darwin","Hobart","Sydney","Melbourne","Perth"])
  42 +x.add_column("Area", [1295, 5905, 112, 1357, 2058, 1566, 5386])
  43 +x.add_column("Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092,
  44 +1554769])
  45 +x.add_column("Annual Rainfall",[600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9,
  46 +869.4])
  47 +
  48 +== Mixing and matching ==
  49 +
  50 +If you really want to, you can even mix and match `add_row` and `add_column`
  51 +and build some of your table in one way and some of it in the other. There's a
  52 +unit test which makes sure that doing things this way will always work out
  53 +nicely as if you'd done it using just one of the two approaches. Tables built
  54 +this way are kind of confusing for other people to read, though, so don't do
  55 +this unless you have a good reason.
  56 +
  57 +== Importing data from a CSV file ==
  58 +
  59 +If you have your table data in a comma separated values file (.csv), you can
  60 +read this data into a PrettyTable like this:
  61 +
  62 +from prettytable import from_csv
  63 +fp = open("myfile.csv", "r")
  64 +mytable = from_csv(fp)
  65 +fp.close()
  66 +
  67 +== Importing data from a HTML string ==
  68 +
  69 +If you have a string containing a HTML <table>, you can read this data into a
  70 +PrettyTable like this:
  71 +
  72 +from prettytable import from_html
  73 +mytable = from_html(html_string)
  74 +
  75 +== Importing data from a database cursor ==
  76 +
  77 +If you have your table data in a database which you can access using a library
  78 +which confirms to the Python DB-API (e.g. an SQLite database accessible using
  79 +the sqlite module), then you can build a PrettyTable using a cursor object,
  80 +like this:
  81 +
  82 +import sqlite3
  83 +from prettytable import from_db_cursor
  84 +
  85 +connection = sqlite3.connect("mydb.db")
  86 +cursor = connection.cursor()
  87 +cursor.execute("SELECT field1, field2, field3 FROM my_table")
  88 +mytable = from_db_cursor(cursor)
  89 +
  90 +== Getting data out ==
  91 +
  92 +There are three ways to get data out of a PrettyTable, in increasing order of
  93 +completeness:
  94 +
  95 + * The `del_row` method takes an integer index of a single row to delete.
  96 + * The `clear_rows` method takes no arguments and deletes all the rows in the
  97 +table - but keeps the field names as they were so you that you can repopulate
  98 +it with the same kind of data.
  99 + * The `clear` method takes no arguments and deletes all rows and all field
  100 +names. It's not quite the same as creating a fresh table instance, though -
  101 +style related settings, discussed later, are maintained.
  102 +
  103 += Displaying your table in ASCII form =
  104 +
  105 +PrettyTable's main goal is to let you print tables in an attractive ASCII form,
  106 +like this:
  107 +
  108 ++-----------+------+------------+-----------------+
  109 +| City name | Area | Population | Annual Rainfall |
  110 ++-----------+------+------------+-----------------+
  111 +| Adelaide | 1295 | 1158259 | 600.5 |
  112 +| Brisbane | 5905 | 1857594 | 1146.4 |
  113 +| Darwin | 112 | 120900 | 1714.7 |
  114 +| Hobart | 1357 | 205556 | 619.5 |
  115 +| Melbourne | 1566 | 3806092 | 646.9 |
  116 +| Perth | 5386 | 1554769 | 869.4 |
  117 +| Sydney | 2058 | 4336374 | 1214.8 |
  118 ++-----------+------+------------+-----------------+
  119 +
  120 +You can print tables like this to `stdout` or get string representations of
  121 +them.
  122 +
  123 +== Printing ==
  124 +
  125 +To print a table in ASCII form, you can just do this:
  126 +
  127 +print x
  128 +
  129 +in Python 2.x or:
  130 +
  131 +print(x)
  132 +
  133 +in Python 3.x.
  134 +
  135 +The old x.printt() method from versions 0.5 and earlier has been removed.
  136 +
  137 +To pass options changing the look of the table, use the get_string() method
  138 +documented below:
  139 +
  140 +print x.get_string()
  141 +
  142 +== Stringing ==
  143 +
  144 +If you don't want to actually print your table in ASCII form but just get a
  145 +string containing what _would_ be printed if you use "print x", you can use
  146 +the `get_string` method:
  147 +
  148 +mystring = x.get_string()
  149 +
  150 +This string is guaranteed to look exactly the same as what would be printed by
  151 +doing "print x". You can now do all the usual things you can do with a
  152 +string, like write your table to a file or insert it into a GUI.
  153 +
  154 +== Controlling which data gets displayed ==
  155 +
  156 +If you like, you can restrict the output of `print x` or `x.get_string` to
  157 +only the fields or rows you like.
  158 +
  159 +The `fields` argument to these methods takes a list of field names to be
  160 +printed:
  161 +
  162 +print x.get_string(fields=["City name", "Population"])
  163 +
  164 +gives:
  165 +
  166 ++-----------+------------+
  167 +| City name | Population |
  168 ++-----------+------------+
  169 +| Adelaide | 1158259 |
  170 +| Brisbane | 1857594 |
  171 +| Darwin | 120900 |
  172 +| Hobart | 205556 |
  173 +| Melbourne | 3806092 |
  174 +| Perth | 1554769 |
  175 +| Sydney | 4336374 |
  176 ++-----------+------------+
  177 +
  178 +The `start` and `end` arguments take the index of the first and last row to
  179 +print respectively. Note that the indexing works like Python list slicing - to
  180 +print the 2nd, 3rd and 4th rows of the table, set `start` to 1 (the first row
  181 +is row 0, so the second is row 1) and set `end` to 4 (the index of the 4th row,
  182 +plus 1):
  183 +
  184 +print x.get_string(start=1,end=4)
  185 +
  186 +prints:
  187 +
  188 ++-----------+------+------------+-----------------+
  189 +| City name | Area | Population | Annual Rainfall |
  190 ++-----------+------+------------+-----------------+
  191 +| Brisbane | 5905 | 1857594 | 1146.4 |
  192 +| Darwin | 112 | 120900 | 1714.7 |
  193 +| Hobart | 1357 | 205556 | 619.5 |
  194 ++-----------+------+------------+-----------------+
  195 +
  196 +== Changing the alignment of columns ==
  197 +
  198 +By default, all columns in a table are centre aligned.
  199 +
  200 +=== All columns at once ===
  201 +
  202 +You can change the alignment of all the columns in a table at once by assigning
  203 +a one character string to the `align` attribute. The allowed strings are "l",
  204 +"r" and "c" for left, right and centre alignment, respectively:
  205 +
  206 +x.align = "r"
  207 +print x
  208 +
  209 +gives:
  210 +
  211 ++-----------+------+------------+-----------------+
  212 +| City name | Area | Population | Annual Rainfall |
  213 ++-----------+------+------------+-----------------+
  214 +| Adelaide | 1295 | 1158259 | 600.5 |
  215 +| Brisbane | 5905 | 1857594 | 1146.4 |
  216 +| Darwin | 112 | 120900 | 1714.7 |
  217 +| Hobart | 1357 | 205556 | 619.5 |
  218 +| Melbourne | 1566 | 3806092 | 646.9 |
  219 +| Perth | 5386 | 1554769 | 869.4 |
  220 +| Sydney | 2058 | 4336374 | 1214.8 |
  221 ++-----------+------+------------+-----------------+
  222 +
  223 +=== One column at a time ===
  224 +
  225 +You can also change the alignment of individual columns based on the
  226 +corresponding field name by treating the `align` attribute as if it were a
  227 +dictionary.
  228 +
  229 +x.align["City name"] = "l"
  230 +x.align["Area"] = "c"
  231 +x.align["Population"] = "r"
  232 +x.align["Annual Rainfall"] = "c"
  233 +print x
  234 +
  235 +gives:
  236 +
  237 ++-----------+------+------------+-----------------+
  238 +| City name | Area | Population | Annual Rainfall |
  239 ++-----------+------+------------+-----------------+
  240 +| Adelaide | 1295 | 1158259 | 600.5 |
  241 +| Brisbane | 5905 | 1857594 | 1146.4 |
  242 +| Darwin | 112 | 120900 | 1714.7 |
  243 +| Hobart | 1357 | 205556 | 619.5 |
  244 +| Melbourne | 1566 | 3806092 | 646.9 |
  245 +| Perth | 5386 | 1554769 | 869.4 |
  246 +| Sydney | 2058 | 4336374 | 1214.8 |
  247 ++-----------+------+------------+-----------------+
  248 +
  249 +== Sorting your table by a field ==
  250 +
  251 +You can make sure that your ASCII tables are produced with the data sorted by
  252 +one particular field by giving `get_string` a `sortby` keyword argument, which
  253 + must be a string containing the name of one field.
  254 +
  255 +For example, to print the example table we built earlier of Australian capital
  256 +city data, so that the most populated city comes last, we can do this:
  257 +
  258 +print x.get_string(sortby="Population")
  259 +
  260 +to get
  261 +
  262 ++-----------+------+------------+-----------------+
  263 +| City name | Area | Population | Annual Rainfall |
  264 ++-----------+------+------------+-----------------+
  265 +| Darwin | 112 | 120900 | 1714.7 |
  266 +| Hobart | 1357 | 205556 | 619.5 |
  267 +| Adelaide | 1295 | 1158259 | 600.5 |
  268 +| Perth | 5386 | 1554769 | 869.4 |
  269 +| Brisbane | 5905 | 1857594 | 1146.4 |
  270 +| Melbourne | 1566 | 3806092 | 646.9 |
  271 +| Sydney | 2058 | 4336374 | 1214.8 |
  272 ++-----------+------+------------+-----------------+
  273 +
  274 +If we want the most populated city to come _first_, we can also give a
  275 +`reversesort=True` argument.
  276 +
  277 +If you _always_ want your tables to be sorted in a certain way, you can make
  278 +the setting long term like this:
  279 +
  280 +x.sortby = "Population"
  281 +print x
  282 +print x
  283 +print x
  284 +
  285 +All three tables printed by this code will be sorted by population (you could
  286 +do `x.reversesort = True` as well, if you wanted). The behaviour will persist
  287 +until you turn it off:
  288 +
  289 +x.sortby = None
  290 +
  291 +If you want to specify a custom sorting function, you can use the `sort_key`
  292 +keyword argument. Pass this a function which accepts two lists of values
  293 +and returns a negative or positive value depending on whether the first list
  294 +should appeare before or after the second one. If your table has n columns,
  295 +each list will have n+1 elements. Each list corresponds to one row of the
  296 +table. The first element will be whatever data is in the relevant row, in
  297 +the column specified by the `sort_by` argument. The remaining n elements
  298 +are the data in each of the table's columns, in order, including a repeated
  299 +instance of the data in the `sort_by` column.
  300 +
  301 += Changing the appearance of your table - the easy way =
  302 +
  303 +By default, PrettyTable produces ASCII tables that look like the ones used in
  304 +SQL database shells. But if can print them in a variety of other formats as
  305 +well. If the format you want to use is common, PrettyTable makes this very
  306 +easy for you to do using the `set_style` method. If you want to produce an
  307 +uncommon table, you'll have to do things slightly harder (see later).
  308 +
  309 +== Setting a table style ==
  310 +
  311 +You can set the style for your table using the `set_style` method before any
  312 +calls to `print` or `get_string`. Here's how to print a table in a format
  313 +which works nicely with Microsoft Word's "Convert to table" feature:
  314 +
  315 +from prettytable import MSWORD_FRIENDLY
  316 +x.set_style(MSWORD_FRIENDLY)
  317 +print x
  318 +
  319 +In addition to `MSWORD_FRIENDLY` there are currently two other in-built styles
  320 +you can use for your tables:
  321 +
  322 + * `DEFAULT` - The default look, used to undo any style changes you may have
  323 +made
  324 + * `PLAIN_COLUMN` - A borderless style that works well with command line
  325 +programs for columnar data
  326 +
  327 +Other styles are likely to appear in future releases.
  328 +
  329 += Changing the appearance of your table - the hard way =
  330 +
  331 +If you want to display your table in a style other than one of the in-built
  332 +styles listed above, you'll have to set things up the hard way.
  333 +
  334 +Don't worry, it's not really that hard!
  335 +
  336 +== Style options ==
  337 +
  338 +PrettyTable has a number of style options which control various aspects of how
  339 +tables are displayed. You have the freedom to set each of these options
  340 +individually to whatever you prefer. The `set_style` method just does this
  341 +automatically for you.
  342 +
  343 +The options are these:
  344 +
  345 + * `border` - A boolean option (must be `True` or `False`). Controls whether
  346 + or not a border is drawn around the table.
  347 + * `header` - A boolean option (must be `True` or `False`). Controls whether
  348 + or not the first row of the table is a header showing the names of all the
  349 + fields.
  350 + * `hrules` - Controls printing of horizontal rules after rows. Allowed
  351 + values: FRAME, HEADER, ALL, NONE - note that these are variables defined
  352 + inside the `prettytable` module so make sure you import them or use
  353 + `prettytable.FRAME` etc.
  354 + * `vrules` - Controls printing of vertical rules between columns. Allowed
  355 + values: FRAME, ALL, NONE.
  356 + * `int_format` - A string which controls the way integer data is printed.
  357 + This works like: print "%<int_format>d" % data
  358 + * `float_format` - A string which controls the way floating point data is
  359 + printed. This works like: print "%<int_format>f" % data
  360 + * `padding_width` - Number of spaces on either side of column data (only used
  361 + if left and right paddings are None).
  362 + * `left_padding_width` - Number of spaces on left hand side of column data.
  363 + * `right_padding_width` - Number of spaces on right hand side of column data.
  364 + * `vertical_char` - Single character string used to draw vertical lines.
  365 + Default is `|`.
  366 + * `horizontal_char` - Single character string used to draw horizontal lines.
  367 + Default is `-`.
  368 + * `junction_char` - Single character string used to draw line junctions.
  369 + Default is `+`.
  370 +
  371 +You can set the style options to your own settings in two ways:
  372 +
  373 +== Setting style options for the long term ==
  374 +
  375 +If you want to print your table with a different style several times, you can
  376 +set your option for the "long term" just by changing the appropriate
  377 +attributes. If you never want your tables to have borders you can do this:
  378 +
  379 +x.border = False
  380 +print x
  381 +print x
  382 +print x
  383 +
  384 +Neither of the 3 tables printed by this will have borders, even if you do
  385 +things like add extra rows inbetween them. The lack of borders will last until
  386 +you do:
  387 +
  388 +x.border = True
  389 +
  390 +to turn them on again. This sort of long term setting is exactly how
  391 +`set_style` works. `set_style` just sets a bunch of attributes to pre-set
  392 +values for you.
  393 +
  394 +Note that if you know what style options you want at the moment you are
  395 +creating your table, you can specify them using keyword arguments to the
  396 +constructor. For example, the following two code blocks are equivalent:
  397 +
  398 +x = PrettyTable()
  399 +x.border = False
  400 +x.header = False
  401 +x.padding_width = 5
  402 +
  403 +x = PrettyTable(border=False, header=False, padding_width=5)
  404 +
  405 +== Changing style options just once ==
  406 +
  407 +If you don't want to make long term style changes by changing an attribute like
  408 +in the previous section, you can make changes that last for just one
  409 +``get_string`` by giving those methods keyword arguments. To print two
  410 +"normal" tables with one borderless table between them, you could do this:
  411 +
  412 +print x
  413 +print x.get_string(border=False)
  414 +print x
  415 +
  416 += Displaying your table in HTML form =
  417 +
  418 +PrettyTable will also print your tables in HTML form, as `<table>`s. Just like
  419 +in ASCII form, you can actually print your table - just use `print_html()` - or
  420 +get a string representation - just use `get_html_string()`. HTML printing
  421 +supports the `fields`, `start`, `end`, `sortby` and `reversesort` arguments in
  422 +exactly the same way as ASCII printing.
  423 +
  424 +== Styling HTML tables ==
  425 +
  426 +By default, PrettyTable outputs HTML for "vanilla" tables. The HTML code is
  427 +quite simple. It looks like this:
  428 +
  429 +<table>
  430 + <tr>
  431 + <th>City name</th>
  432 + <th>Area</th>
  433 + <th>Population</th>
  434 + <th>Annual Rainfall</th>
  435 + </tr>
  436 + <tr>
  437 + <td>Adelaide</td>
  438 + <td>1295</td>
  439 + <td>1158259</td>
  440 + <td>600.5</td>
  441 + <tr>
  442 + <td>Brisbane</td>
  443 + <td>5905</td>
  444 + <td>1857594</td>
  445 + <td>1146.4</td>
  446 + ...
  447 + ...
  448 + ...
  449 +</table>
  450 +
  451 +If you like, you can ask PrettyTable to do its best to mimick the style options
  452 +that your table has set using inline CSS. This is done by giving a
  453 +`format=True` keyword argument to either the `print_html` or `get_html_string`
  454 +methods. Note that if you _always_ want to print formatted HTML you can do:
  455 +
  456 +x.format = True
  457 +
  458 +and the setting will persist until you turn it off.
  459 +
  460 +Just like with ASCII tables, if you want to change the table's style for just
  461 +one `print_html` or one `get_html_string` you can pass those methods keyword
  462 +arguments - exactly like `print` and `get_string`.
  463 +
  464 +== Setting HTML attributes ==
  465 +
  466 +You can provide a dictionary of HTML attribute name/value pairs to the
  467 +`print_html` and `get_html_string` methods using the `attributes` keyword
  468 +argument. This lets you specify common HTML attributes like `name`, `id` and
  469 +`class` that can be used for linking to your tables or customising their
  470 +appearance using CSS. For example:
  471 +
  472 +x.print_html(attributes={"name":"my_table", "class":"red_table"})
  473 +
  474 +will print:
  475 +
  476 +<table name="my_table" class="red_table">
  477 + <tr>
  478 + <th>City name</th>
  479 + <th>Area</th>
  480 + <th>Population</th>
  481 + <th>Annual Rainfall</th>
  482 + </tr>
  483 + ...
  484 + ...
  485 + ...
  486 +</table>
  487 +
  488 += Miscellaneous things =
  489 +
  490 +== Copying a table ==
  491 +
  492 +You can call the `copy` method on a PrettyTable object without arguments to
  493 +return an identical independent copy of the table.
  494 +
  495 +If you want a copy of a PrettyTable object with just a subset of the rows,
  496 +you can use list slicing notation:
  497 +
  498 +new_table = old_table[0:5]
... ...
oletools/thirdparty/prettytable/__init__.py 0 → 100644
oletools/thirdparty/prettytable/prettytable.py 0 → 100644
  1 +#!/usr/bin/env python
  2 +#
  3 +# Copyright (c) 2009-2013, Luke Maurits <luke@maurits.id.au>
  4 +# All rights reserved.
  5 +# With contributions from:
  6 +# * Chris Clark
  7 +# * Klein Stephane
  8 +#
  9 +# Redistribution and use in source and binary forms, with or without
  10 +# modification, are permitted provided that the following conditions are met:
  11 +#
  12 +# * Redistributions of source code must retain the above copyright notice,
  13 +# this list of conditions and the following disclaimer.
  14 +# * Redistributions in binary form must reproduce the above copyright notice,
  15 +# this list of conditions and the following disclaimer in the documentation
  16 +# and/or other materials provided with the distribution.
  17 +# * The name of the author may not be used to endorse or promote products
  18 +# derived from this software without specific prior written permission.
  19 +#
  20 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21 +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22 +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23 +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  24 +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25 +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26 +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27 +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28 +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29 +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30 +# POSSIBILITY OF SUCH DAMAGE.
  31 +
  32 +__version__ = "0.7.2"
  33 +
  34 +import copy
  35 +import csv
  36 +import random
  37 +import re
  38 +import sys
  39 +import textwrap
  40 +import itertools
  41 +import unicodedata
  42 +
  43 +py3k = sys.version_info[0] >= 3
  44 +if py3k:
  45 + unicode = str
  46 + basestring = str
  47 + itermap = map
  48 + iterzip = zip
  49 + uni_chr = chr
  50 + from html.parser import HTMLParser
  51 +else:
  52 + itermap = itertools.imap
  53 + iterzip = itertools.izip
  54 + uni_chr = unichr
  55 + from HTMLParser import HTMLParser
  56 +
  57 +if py3k and sys.version_info[1] >= 2:
  58 + from html import escape
  59 +else:
  60 + from cgi import escape
  61 +
  62 +# hrule styles
  63 +FRAME = 0
  64 +ALL = 1
  65 +NONE = 2
  66 +HEADER = 3
  67 +
  68 +# Table styles
  69 +DEFAULT = 10
  70 +MSWORD_FRIENDLY = 11
  71 +PLAIN_COLUMNS = 12
  72 +RANDOM = 20
  73 +
  74 +_re = re.compile("\033\[[0-9;]*m")
  75 +
  76 +def _get_size(text):
  77 + lines = text.split("\n")
  78 + height = len(lines)
  79 + width = max([_str_block_width(line) for line in lines])
  80 + return (width, height)
  81 +
  82 +class PrettyTable(object):
  83 +
  84 + def __init__(self, field_names=None, **kwargs):
  85 +
  86 + """Return a new PrettyTable instance
  87 +
  88 + Arguments:
  89 +
  90 + encoding - Unicode encoding scheme used to decode any encoded input
  91 + field_names - list or tuple of field names
  92 + fields - list or tuple of field names to include in displays
  93 + start - index of first data row to include in output
  94 + end - index of last data row to include in output PLUS ONE (list slice style)
  95 + header - print a header showing field names (True or False)
  96 + header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)
  97 + border - print a border around the table (True or False)
  98 + hrules - controls printing of horizontal rules after rows. Allowed values: FRAME, HEADER, ALL, NONE
  99 + vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
  100 + int_format - controls formatting of integer data
  101 + float_format - controls formatting of floating point data
  102 + padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
  103 + left_padding_width - number of spaces on left hand side of column data
  104 + right_padding_width - number of spaces on right hand side of column data
  105 + vertical_char - single character string used to draw vertical lines
  106 + horizontal_char - single character string used to draw horizontal lines
  107 + junction_char - single character string used to draw line junctions
  108 + sortby - name of field to sort rows by
  109 + sort_key - sorting key function, applied to data points before sorting
  110 + valign - default valign for each row (None, "t", "m" or "b")
  111 + reversesort - True or False to sort in descending or ascending order"""
  112 +
  113 + self.encoding = kwargs.get("encoding", "UTF-8")
  114 +
  115 + # Data
  116 + self._field_names = []
  117 + self._align = {}
  118 + self._valign = {}
  119 + self._max_width = {}
  120 + self._rows = []
  121 + if field_names:
  122 + self.field_names = field_names
  123 + else:
  124 + self._widths = []
  125 +
  126 + # Options
  127 + self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split()
  128 + self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split())
  129 + self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split())
  130 + for option in self._options:
  131 + if option in kwargs:
  132 + self._validate_option(option, kwargs[option])
  133 + else:
  134 + kwargs[option] = None
  135 +
  136 + self._start = kwargs["start"] or 0
  137 + self._end = kwargs["end"] or None
  138 + self._fields = kwargs["fields"] or None
  139 +
  140 + if kwargs["header"] in (True, False):
  141 + self._header = kwargs["header"]
  142 + else:
  143 + self._header = True
  144 + self._header_style = kwargs["header_style"] or None
  145 + if kwargs["border"] in (True, False):
  146 + self._border = kwargs["border"]
  147 + else:
  148 + self._border = True
  149 + self._hrules = kwargs["hrules"] or FRAME
  150 + self._vrules = kwargs["vrules"] or ALL
  151 +
  152 + self._sortby = kwargs["sortby"] or None
  153 + if kwargs["reversesort"] in (True, False):
  154 + self._reversesort = kwargs["reversesort"]
  155 + else:
  156 + self._reversesort = False
  157 + self._sort_key = kwargs["sort_key"] or (lambda x: x)
  158 +
  159 + self._int_format = kwargs["int_format"] or {}
  160 + self._float_format = kwargs["float_format"] or {}
  161 + self._padding_width = kwargs["padding_width"] or 1
  162 + self._left_padding_width = kwargs["left_padding_width"] or None
  163 + self._right_padding_width = kwargs["right_padding_width"] or None
  164 +
  165 + self._vertical_char = kwargs["vertical_char"] or self._unicode("|")
  166 + self._horizontal_char = kwargs["horizontal_char"] or self._unicode("-")
  167 + self._junction_char = kwargs["junction_char"] or self._unicode("+")
  168 +
  169 + if kwargs["print_empty"] in (True, False):
  170 + self._print_empty = kwargs["print_empty"]
  171 + else:
  172 + self._print_empty = True
  173 + self._format = kwargs["format"] or False
  174 + self._xhtml = kwargs["xhtml"] or False
  175 + self._attributes = kwargs["attributes"] or {}
  176 +
  177 + def _unicode(self, value):
  178 + if not isinstance(value, basestring):
  179 + value = str(value)
  180 + if not isinstance(value, unicode):
  181 + value = unicode(value, self.encoding, "strict")
  182 + return value
  183 +
  184 + def _justify(self, text, width, align):
  185 + excess = width - _str_block_width(text)
  186 + if align == "l":
  187 + return text + excess * " "
  188 + elif align == "r":
  189 + return excess * " " + text
  190 + else:
  191 + if excess % 2:
  192 + # Uneven padding
  193 + # Put more space on right if text is of odd length...
  194 + if _str_block_width(text) % 2:
  195 + return (excess//2)*" " + text + (excess//2 + 1)*" "
  196 + # and more space on left if text is of even length
  197 + else:
  198 + return (excess//2 + 1)*" " + text + (excess//2)*" "
  199 + # Why distribute extra space this way? To match the behaviour of
  200 + # the inbuilt str.center() method.
  201 + else:
  202 + # Equal padding on either side
  203 + return (excess//2)*" " + text + (excess//2)*" "
  204 +
  205 + def __getattr__(self, name):
  206 +
  207 + if name == "rowcount":
  208 + return len(self._rows)
  209 + elif name == "colcount":
  210 + if self._field_names:
  211 + return len(self._field_names)
  212 + elif self._rows:
  213 + return len(self._rows[0])
  214 + else:
  215 + return 0
  216 + else:
  217 + raise AttributeError(name)
  218 +
  219 + def __getitem__(self, index):
  220 +
  221 + new = PrettyTable()
  222 + new.field_names = self.field_names
  223 + for attr in self._options:
  224 + setattr(new, "_"+attr, getattr(self, "_"+attr))
  225 + setattr(new, "_align", getattr(self, "_align"))
  226 + if isinstance(index, slice):
  227 + for row in self._rows[index]:
  228 + new.add_row(row)
  229 + elif isinstance(index, int):
  230 + new.add_row(self._rows[index])
  231 + else:
  232 + raise Exception("Index %s is invalid, must be an integer or slice" % str(index))
  233 + return new
  234 +
  235 + if py3k:
  236 + def __str__(self):
  237 + return self.__unicode__()
  238 + else:
  239 + def __str__(self):
  240 + return self.__unicode__().encode(self.encoding)
  241 +
  242 + def __unicode__(self):
  243 + return self.get_string()
  244 +
  245 + ##############################
  246 + # ATTRIBUTE VALIDATORS #
  247 + ##############################
  248 +
  249 + # The method _validate_option is all that should be used elsewhere in the code base to validate options.
  250 + # It will call the appropriate validation method for that option. The individual validation methods should
  251 + # never need to be called directly (although nothing bad will happen if they *are*).
  252 + # Validation happens in TWO places.
  253 + # Firstly, in the property setters defined in the ATTRIBUTE MANAGMENT section.
  254 + # Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings
  255 +
  256 + def _validate_option(self, option, val):
  257 + if option in ("field_names"):
  258 + self._validate_field_names(val)
  259 + elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"):
  260 + self._validate_nonnegative_int(option, val)
  261 + elif option in ("sortby"):
  262 + self._validate_field_name(option, val)
  263 + elif option in ("sort_key"):
  264 + self._validate_function(option, val)
  265 + elif option in ("hrules"):
  266 + self._validate_hrules(option, val)
  267 + elif option in ("vrules"):
  268 + self._validate_vrules(option, val)
  269 + elif option in ("fields"):
  270 + self._validate_all_field_names(option, val)
  271 + elif option in ("header", "border", "reversesort", "xhtml", "print_empty"):
  272 + self._validate_true_or_false(option, val)
  273 + elif option in ("header_style"):
  274 + self._validate_header_style(val)
  275 + elif option in ("int_format"):
  276 + self._validate_int_format(option, val)
  277 + elif option in ("float_format"):
  278 + self._validate_float_format(option, val)
  279 + elif option in ("vertical_char", "horizontal_char", "junction_char"):
  280 + self._validate_single_char(option, val)
  281 + elif option in ("attributes"):
  282 + self._validate_attributes(option, val)
  283 + else:
  284 + raise Exception("Unrecognised option: %s!" % option)
  285 +
  286 + def _validate_field_names(self, val):
  287 + # Check for appropriate length
  288 + if self._field_names:
  289 + try:
  290 + assert len(val) == len(self._field_names)
  291 + except AssertionError:
  292 + raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names)))
  293 + if self._rows:
  294 + try:
  295 + assert len(val) == len(self._rows[0])
  296 + except AssertionError:
  297 + raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0])))
  298 + # Check for uniqueness
  299 + try:
  300 + assert len(val) == len(set(val))
  301 + except AssertionError:
  302 + raise Exception("Field names must be unique!")
  303 +
  304 + def _validate_header_style(self, val):
  305 + try:
  306 + assert val in ("cap", "title", "upper", "lower", None)
  307 + except AssertionError:
  308 + raise Exception("Invalid header style, use cap, title, upper, lower or None!")
  309 +
  310 + def _validate_align(self, val):
  311 + try:
  312 + assert val in ["l","c","r"]
  313 + except AssertionError:
  314 + raise Exception("Alignment %s is invalid, use l, c or r!" % val)
  315 +
  316 + def _validate_valign(self, val):
  317 + try:
  318 + assert val in ["t","m","b",None]
  319 + except AssertionError:
  320 + raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
  321 +
  322 + def _validate_nonnegative_int(self, name, val):
  323 + try:
  324 + assert int(val) >= 0
  325 + except AssertionError:
  326 + raise Exception("Invalid value for %s: %s!" % (name, self._unicode(val)))
  327 +
  328 + def _validate_true_or_false(self, name, val):
  329 + try:
  330 + assert val in (True, False)
  331 + except AssertionError:
  332 + raise Exception("Invalid value for %s! Must be True or False." % name)
  333 +
  334 + def _validate_int_format(self, name, val):
  335 + if val == "":
  336 + return
  337 + try:
  338 + assert type(val) in (str, unicode)
  339 + assert val.isdigit()
  340 + except AssertionError:
  341 + raise Exception("Invalid value for %s! Must be an integer format string." % name)
  342 +
  343 + def _validate_float_format(self, name, val):
  344 + if val == "":
  345 + return
  346 + try:
  347 + assert type(val) in (str, unicode)
  348 + assert "." in val
  349 + bits = val.split(".")
  350 + assert len(bits) <= 2
  351 + assert bits[0] == "" or bits[0].isdigit()
  352 + assert bits[1] == "" or bits[1].isdigit()
  353 + except AssertionError:
  354 + raise Exception("Invalid value for %s! Must be a float format string." % name)
  355 +
  356 + def _validate_function(self, name, val):
  357 + try:
  358 + assert hasattr(val, "__call__")
  359 + except AssertionError:
  360 + raise Exception("Invalid value for %s! Must be a function." % name)
  361 +
  362 + def _validate_hrules(self, name, val):
  363 + try:
  364 + assert val in (ALL, FRAME, HEADER, NONE)
  365 + except AssertionError:
  366 + raise Exception("Invalid value for %s! Must be ALL, FRAME, HEADER or NONE." % name)
  367 +
  368 + def _validate_vrules(self, name, val):
  369 + try:
  370 + assert val in (ALL, FRAME, NONE)
  371 + except AssertionError:
  372 + raise Exception("Invalid value for %s! Must be ALL, FRAME, or NONE." % name)
  373 +
  374 + def _validate_field_name(self, name, val):
  375 + try:
  376 + assert (val in self._field_names) or (val is None)
  377 + except AssertionError:
  378 + raise Exception("Invalid field name: %s!" % val)
  379 +
  380 + def _validate_all_field_names(self, name, val):
  381 + try:
  382 + for x in val:
  383 + self._validate_field_name(name, x)
  384 + except AssertionError:
  385 + raise Exception("fields must be a sequence of field names!")
  386 +
  387 + def _validate_single_char(self, name, val):
  388 + try:
  389 + assert _str_block_width(val) == 1
  390 + except AssertionError:
  391 + raise Exception("Invalid value for %s! Must be a string of length 1." % name)
  392 +
  393 + def _validate_attributes(self, name, val):
  394 + try:
  395 + assert isinstance(val, dict)
  396 + except AssertionError:
  397 + raise Exception("attributes must be a dictionary of name/value pairs!")
  398 +
  399 + ##############################
  400 + # ATTRIBUTE MANAGEMENT #
  401 + ##############################
  402 +
  403 + def _get_field_names(self):
  404 + return self._field_names
  405 + """The names of the fields
  406 +
  407 + Arguments:
  408 +
  409 + fields - list or tuple of field names"""
  410 + def _set_field_names(self, val):
  411 + val = [self._unicode(x) for x in val]
  412 + self._validate_option("field_names", val)
  413 + if self._field_names:
  414 + old_names = self._field_names[:]
  415 + self._field_names = val
  416 + if self._align and old_names:
  417 + for old_name, new_name in zip(old_names, val):
  418 + self._align[new_name] = self._align[old_name]
  419 + for old_name in old_names:
  420 + if old_name not in self._align:
  421 + self._align.pop(old_name)
  422 + else:
  423 + for field in self._field_names:
  424 + self._align[field] = "c"
  425 + if self._valign and old_names:
  426 + for old_name, new_name in zip(old_names, val):
  427 + self._valign[new_name] = self._valign[old_name]
  428 + for old_name in old_names:
  429 + if old_name not in self._valign:
  430 + self._valign.pop(old_name)
  431 + else:
  432 + for field in self._field_names:
  433 + self._valign[field] = "t"
  434 + field_names = property(_get_field_names, _set_field_names)
  435 +
  436 + def _get_align(self):
  437 + return self._align
  438 + def _set_align(self, val):
  439 + self._validate_align(val)
  440 + for field in self._field_names:
  441 + self._align[field] = val
  442 + align = property(_get_align, _set_align)
  443 +
  444 + def _get_valign(self):
  445 + return self._valign
  446 + def _set_valign(self, val):
  447 + self._validate_valign(val)
  448 + for field in self._field_names:
  449 + self._valign[field] = val
  450 + valign = property(_get_valign, _set_valign)
  451 +
  452 + def _get_max_width(self):
  453 + return self._max_width
  454 + def _set_max_width(self, val):
  455 + self._validate_option("max_width", val)
  456 + for field in self._field_names:
  457 + self._max_width[field] = val
  458 + max_width = property(_get_max_width, _set_max_width)
  459 +
  460 + def _get_fields(self):
  461 + """List or tuple of field names to include in displays
  462 +
  463 + Arguments:
  464 +
  465 + fields - list or tuple of field names to include in displays"""
  466 + return self._fields
  467 + def _set_fields(self, val):
  468 + self._validate_option("fields", val)
  469 + self._fields = val
  470 + fields = property(_get_fields, _set_fields)
  471 +
  472 + def _get_start(self):
  473 + """Start index of the range of rows to print
  474 +
  475 + Arguments:
  476 +
  477 + start - index of first data row to include in output"""
  478 + return self._start
  479 +
  480 + def _set_start(self, val):
  481 + self._validate_option("start", val)
  482 + self._start = val
  483 + start = property(_get_start, _set_start)
  484 +
  485 + def _get_end(self):
  486 + """End index of the range of rows to print
  487 +
  488 + Arguments:
  489 +
  490 + end - index of last data row to include in output PLUS ONE (list slice style)"""
  491 + return self._end
  492 + def _set_end(self, val):
  493 + self._validate_option("end", val)
  494 + self._end = val
  495 + end = property(_get_end, _set_end)
  496 +
  497 + def _get_sortby(self):
  498 + """Name of field by which to sort rows
  499 +
  500 + Arguments:
  501 +
  502 + sortby - field name to sort by"""
  503 + return self._sortby
  504 + def _set_sortby(self, val):
  505 + self._validate_option("sortby", val)
  506 + self._sortby = val
  507 + sortby = property(_get_sortby, _set_sortby)
  508 +
  509 + def _get_reversesort(self):
  510 + """Controls direction of sorting (ascending vs descending)
  511 +
  512 + Arguments:
  513 +
  514 + reveresort - set to True to sort by descending order, or False to sort by ascending order"""
  515 + return self._reversesort
  516 + def _set_reversesort(self, val):
  517 + self._validate_option("reversesort", val)
  518 + self._reversesort = val
  519 + reversesort = property(_get_reversesort, _set_reversesort)
  520 +
  521 + def _get_sort_key(self):
  522 + """Sorting key function, applied to data points before sorting
  523 +
  524 + Arguments:
  525 +
  526 + sort_key - a function which takes one argument and returns something to be sorted"""
  527 + return self._sort_key
  528 + def _set_sort_key(self, val):
  529 + self._validate_option("sort_key", val)
  530 + self._sort_key = val
  531 + sort_key = property(_get_sort_key, _set_sort_key)
  532 +
  533 + def _get_header(self):
  534 + """Controls printing of table header with field names
  535 +
  536 + Arguments:
  537 +
  538 + header - print a header showing field names (True or False)"""
  539 + return self._header
  540 + def _set_header(self, val):
  541 + self._validate_option("header", val)
  542 + self._header = val
  543 + header = property(_get_header, _set_header)
  544 +
  545 + def _get_header_style(self):
  546 + """Controls stylisation applied to field names in header
  547 +
  548 + Arguments:
  549 +
  550 + header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)"""
  551 + return self._header_style
  552 + def _set_header_style(self, val):
  553 + self._validate_header_style(val)
  554 + self._header_style = val
  555 + header_style = property(_get_header_style, _set_header_style)
  556 +
  557 + def _get_border(self):
  558 + """Controls printing of border around table
  559 +
  560 + Arguments:
  561 +
  562 + border - print a border around the table (True or False)"""
  563 + return self._border
  564 + def _set_border(self, val):
  565 + self._validate_option("border", val)
  566 + self._border = val
  567 + border = property(_get_border, _set_border)
  568 +
  569 + def _get_hrules(self):
  570 + """Controls printing of horizontal rules after rows
  571 +
  572 + Arguments:
  573 +
  574 + hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
  575 + return self._hrules
  576 + def _set_hrules(self, val):
  577 + self._validate_option("hrules", val)
  578 + self._hrules = val
  579 + hrules = property(_get_hrules, _set_hrules)
  580 +
  581 + def _get_vrules(self):
  582 + """Controls printing of vertical rules between columns
  583 +
  584 + Arguments:
  585 +
  586 + vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
  587 + return self._vrules
  588 + def _set_vrules(self, val):
  589 + self._validate_option("vrules", val)
  590 + self._vrules = val
  591 + vrules = property(_get_vrules, _set_vrules)
  592 +
  593 + def _get_int_format(self):
  594 + """Controls formatting of integer data
  595 + Arguments:
  596 +
  597 + int_format - integer format string"""
  598 + return self._int_format
  599 + def _set_int_format(self, val):
  600 +# self._validate_option("int_format", val)
  601 + for field in self._field_names:
  602 + self._int_format[field] = val
  603 + int_format = property(_get_int_format, _set_int_format)
  604 +
  605 + def _get_float_format(self):
  606 + """Controls formatting of floating point data
  607 + Arguments:
  608 +
  609 + float_format - floating point format string"""
  610 + return self._float_format
  611 + def _set_float_format(self, val):
  612 +# self._validate_option("float_format", val)
  613 + for field in self._field_names:
  614 + self._float_format[field] = val
  615 + float_format = property(_get_float_format, _set_float_format)
  616 +
  617 + def _get_padding_width(self):
  618 + """The number of empty spaces between a column's edge and its content
  619 +
  620 + Arguments:
  621 +
  622 + padding_width - number of spaces, must be a positive integer"""
  623 + return self._padding_width
  624 + def _set_padding_width(self, val):
  625 + self._validate_option("padding_width", val)
  626 + self._padding_width = val
  627 + padding_width = property(_get_padding_width, _set_padding_width)
  628 +
  629 + def _get_left_padding_width(self):
  630 + """The number of empty spaces between a column's left edge and its content
  631 +
  632 + Arguments:
  633 +
  634 + left_padding - number of spaces, must be a positive integer"""
  635 + return self._left_padding_width
  636 + def _set_left_padding_width(self, val):
  637 + self._validate_option("left_padding_width", val)
  638 + self._left_padding_width = val
  639 + left_padding_width = property(_get_left_padding_width, _set_left_padding_width)
  640 +
  641 + def _get_right_padding_width(self):
  642 + """The number of empty spaces between a column's right edge and its content
  643 +
  644 + Arguments:
  645 +
  646 + right_padding - number of spaces, must be a positive integer"""
  647 + return self._right_padding_width
  648 + def _set_right_padding_width(self, val):
  649 + self._validate_option("right_padding_width", val)
  650 + self._right_padding_width = val
  651 + right_padding_width = property(_get_right_padding_width, _set_right_padding_width)
  652 +
  653 + def _get_vertical_char(self):
  654 + """The charcter used when printing table borders to draw vertical lines
  655 +
  656 + Arguments:
  657 +
  658 + vertical_char - single character string used to draw vertical lines"""
  659 + return self._vertical_char
  660 + def _set_vertical_char(self, val):
  661 + val = self._unicode(val)
  662 + self._validate_option("vertical_char", val)
  663 + self._vertical_char = val
  664 + vertical_char = property(_get_vertical_char, _set_vertical_char)
  665 +
  666 + def _get_horizontal_char(self):
  667 + """The charcter used when printing table borders to draw horizontal lines
  668 +
  669 + Arguments:
  670 +
  671 + horizontal_char - single character string used to draw horizontal lines"""
  672 + return self._horizontal_char
  673 + def _set_horizontal_char(self, val):
  674 + val = self._unicode(val)
  675 + self._validate_option("horizontal_char", val)
  676 + self._horizontal_char = val
  677 + horizontal_char = property(_get_horizontal_char, _set_horizontal_char)
  678 +
  679 + def _get_junction_char(self):
  680 + """The charcter used when printing table borders to draw line junctions
  681 +
  682 + Arguments:
  683 +
  684 + junction_char - single character string used to draw line junctions"""
  685 + return self._junction_char
  686 + def _set_junction_char(self, val):
  687 + val = self._unicode(val)
  688 + self._validate_option("vertical_char", val)
  689 + self._junction_char = val
  690 + junction_char = property(_get_junction_char, _set_junction_char)
  691 +
  692 + def _get_format(self):
  693 + """Controls whether or not HTML tables are formatted to match styling options
  694 +
  695 + Arguments:
  696 +
  697 + format - True or False"""
  698 + return self._format
  699 + def _set_format(self, val):
  700 + self._validate_option("format", val)
  701 + self._format = val
  702 + format = property(_get_format, _set_format)
  703 +
  704 + def _get_print_empty(self):
  705 + """Controls whether or not empty tables produce a header and frame or just an empty string
  706 +
  707 + Arguments:
  708 +
  709 + print_empty - True or False"""
  710 + return self._print_empty
  711 + def _set_print_empty(self, val):
  712 + self._validate_option("print_empty", val)
  713 + self._print_empty = val
  714 + print_empty = property(_get_print_empty, _set_print_empty)
  715 +
  716 + def _get_attributes(self):
  717 + """A dictionary of HTML attribute name/value pairs to be included in the <table> tag when printing HTML
  718 +
  719 + Arguments:
  720 +
  721 + attributes - dictionary of attributes"""
  722 + return self._attributes
  723 + def _set_attributes(self, val):
  724 + self._validate_option("attributes", val)
  725 + self._attributes = val
  726 + attributes = property(_get_attributes, _set_attributes)
  727 +
  728 + ##############################
  729 + # OPTION MIXER #
  730 + ##############################
  731 +
  732 + def _get_options(self, kwargs):
  733 +
  734 + options = {}
  735 + for option in self._options:
  736 + if option in kwargs:
  737 + self._validate_option(option, kwargs[option])
  738 + options[option] = kwargs[option]
  739 + else:
  740 + options[option] = getattr(self, "_"+option)
  741 + return options
  742 +
  743 + ##############################
  744 + # PRESET STYLE LOGIC #
  745 + ##############################
  746 +
  747 + def set_style(self, style):
  748 +
  749 + if style == DEFAULT:
  750 + self._set_default_style()
  751 + elif style == MSWORD_FRIENDLY:
  752 + self._set_msword_style()
  753 + elif style == PLAIN_COLUMNS:
  754 + self._set_columns_style()
  755 + elif style == RANDOM:
  756 + self._set_random_style()
  757 + else:
  758 + raise Exception("Invalid pre-set style!")
  759 +
  760 + def _set_default_style(self):
  761 +
  762 + self.header = True
  763 + self.border = True
  764 + self._hrules = FRAME
  765 + self._vrules = ALL
  766 + self.padding_width = 1
  767 + self.left_padding_width = 1
  768 + self.right_padding_width = 1
  769 + self.vertical_char = "|"
  770 + self.horizontal_char = "-"
  771 + self.junction_char = "+"
  772 +
  773 + def _set_msword_style(self):
  774 +
  775 + self.header = True
  776 + self.border = True
  777 + self._hrules = NONE
  778 + self.padding_width = 1
  779 + self.left_padding_width = 1
  780 + self.right_padding_width = 1
  781 + self.vertical_char = "|"
  782 +
  783 + def _set_columns_style(self):
  784 +
  785 + self.header = True
  786 + self.border = False
  787 + self.padding_width = 1
  788 + self.left_padding_width = 0
  789 + self.right_padding_width = 8
  790 +
  791 + def _set_random_style(self):
  792 +
  793 + # Just for fun!
  794 + self.header = random.choice((True, False))
  795 + self.border = random.choice((True, False))
  796 + self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
  797 + self._vrules = random.choice((ALL, FRAME, NONE))
  798 + self.left_padding_width = random.randint(0,5)
  799 + self.right_padding_width = random.randint(0,5)
  800 + self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
  801 + self.horizontal_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
  802 + self.junction_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
  803 +
  804 + ##############################
  805 + # DATA INPUT METHODS #
  806 + ##############################
  807 +
  808 + def add_row(self, row):
  809 +
  810 + """Add a row to the table
  811 +
  812 + Arguments:
  813 +
  814 + row - row of data, should be a list with as many elements as the table
  815 + has fields"""
  816 +
  817 + if self._field_names and len(row) != len(self._field_names):
  818 + raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names)))
  819 + if not self._field_names:
  820 + self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))]
  821 + self._rows.append(list(row))
  822 +
  823 + def del_row(self, row_index):
  824 +
  825 + """Delete a row to the table
  826 +
  827 + Arguments:
  828 +
  829 + row_index - The index of the row you want to delete. Indexing starts at 0."""
  830 +
  831 + if row_index > len(self._rows)-1:
  832 + raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
  833 + del self._rows[row_index]
  834 +
  835 + def add_column(self, fieldname, column, align="c", valign="t"):
  836 +
  837 + """Add a column to the table.
  838 +
  839 + Arguments:
  840 +
  841 + fieldname - name of the field to contain the new column of data
  842 + column - column of data, should be a list with as many elements as the
  843 + table has rows
  844 + align - desired alignment for this column - "l" for left, "c" for centre and "r" for right
  845 + valign - desired vertical alignment for new columns - "t" for top, "m" for middle and "b" for bottom"""
  846 +
  847 + if len(self._rows) in (0, len(column)):
  848 + self._validate_align(align)
  849 + self._validate_valign(valign)
  850 + self._field_names.append(fieldname)
  851 + self._align[fieldname] = align
  852 + self._valign[fieldname] = valign
  853 + for i in range(0, len(column)):
  854 + if len(self._rows) < i+1:
  855 + self._rows.append([])
  856 + self._rows[i].append(column[i])
  857 + else:
  858 + raise Exception("Column length %d does not match number of rows %d!" % (len(column), len(self._rows)))
  859 +
  860 + def clear_rows(self):
  861 +
  862 + """Delete all rows from the table but keep the current field names"""
  863 +
  864 + self._rows = []
  865 +
  866 + def clear(self):
  867 +
  868 + """Delete all rows and field names from the table, maintaining nothing but styling options"""
  869 +
  870 + self._rows = []
  871 + self._field_names = []
  872 + self._widths = []
  873 +
  874 + ##############################
  875 + # MISC PUBLIC METHODS #
  876 + ##############################
  877 +
  878 + def copy(self):
  879 + return copy.deepcopy(self)
  880 +
  881 + ##############################
  882 + # MISC PRIVATE METHODS #
  883 + ##############################
  884 +
  885 + def _format_value(self, field, value):
  886 + if isinstance(value, int) and field in self._int_format:
  887 + value = self._unicode(("%%%sd" % self._int_format[field]) % value)
  888 + elif isinstance(value, float) and field in self._float_format:
  889 + value = self._unicode(("%%%sf" % self._float_format[field]) % value)
  890 + return self._unicode(value)
  891 +
  892 + def _compute_widths(self, rows, options):
  893 + if options["header"]:
  894 + widths = [_get_size(field)[0] for field in self._field_names]
  895 + else:
  896 + widths = len(self.field_names) * [0]
  897 + for row in rows:
  898 + for index, value in enumerate(row):
  899 + fieldname = self.field_names[index]
  900 + if fieldname in self.max_width:
  901 + widths[index] = max(widths[index], min(_get_size(value)[0], self.max_width[fieldname]))
  902 + else:
  903 + widths[index] = max(widths[index], _get_size(value)[0])
  904 + self._widths = widths
  905 +
  906 + def _get_padding_widths(self, options):
  907 +
  908 + if options["left_padding_width"] is not None:
  909 + lpad = options["left_padding_width"]
  910 + else:
  911 + lpad = options["padding_width"]
  912 + if options["right_padding_width"] is not None:
  913 + rpad = options["right_padding_width"]
  914 + else:
  915 + rpad = options["padding_width"]
  916 + return lpad, rpad
  917 +
  918 + def _get_rows(self, options):
  919 + """Return only those data rows that should be printed, based on slicing and sorting.
  920 +
  921 + Arguments:
  922 +
  923 + options - dictionary of option settings."""
  924 +
  925 + # Make a copy of only those rows in the slice range
  926 + rows = copy.deepcopy(self._rows[options["start"]:options["end"]])
  927 + # Sort if necessary
  928 + if options["sortby"]:
  929 + sortindex = self._field_names.index(options["sortby"])
  930 + # Decorate
  931 + rows = [[row[sortindex]]+row for row in rows]
  932 + # Sort
  933 + rows.sort(reverse=options["reversesort"], key=options["sort_key"])
  934 + # Undecorate
  935 + rows = [row[1:] for row in rows]
  936 + return rows
  937 +
  938 + def _format_row(self, row, options):
  939 + return [self._format_value(field, value) for (field, value) in zip(self._field_names, row)]
  940 +
  941 + def _format_rows(self, rows, options):
  942 + return [self._format_row(row, options) for row in rows]
  943 +
  944 + ##############################
  945 + # PLAIN TEXT STRING METHODS #
  946 + ##############################
  947 +
  948 + def get_string(self, **kwargs):
  949 +
  950 + """Return string representation of table in current state.
  951 +
  952 + Arguments:
  953 +
  954 + start - index of first data row to include in output
  955 + end - index of last data row to include in output PLUS ONE (list slice style)
  956 + fields - names of fields (columns) to include
  957 + header - print a header showing field names (True or False)
  958 + border - print a border around the table (True or False)
  959 + hrules - controls printing of horizontal rules after rows. Allowed values: ALL, FRAME, HEADER, NONE
  960 + vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
  961 + int_format - controls formatting of integer data
  962 + float_format - controls formatting of floating point data
  963 + padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
  964 + left_padding_width - number of spaces on left hand side of column data
  965 + right_padding_width - number of spaces on right hand side of column data
  966 + vertical_char - single character string used to draw vertical lines
  967 + horizontal_char - single character string used to draw horizontal lines
  968 + junction_char - single character string used to draw line junctions
  969 + sortby - name of field to sort rows by
  970 + sort_key - sorting key function, applied to data points before sorting
  971 + reversesort - True or False to sort in descending or ascending order
  972 + print empty - if True, stringify just the header for an empty table, if False return an empty string """
  973 +
  974 + options = self._get_options(kwargs)
  975 +
  976 + lines = []
  977 +
  978 + # Don't think too hard about an empty table
  979 + # Is this the desired behaviour? Maybe we should still print the header?
  980 + if self.rowcount == 0 and (not options["print_empty"] or not options["border"]):
  981 + return ""
  982 +
  983 + # Get the rows we need to print, taking into account slicing, sorting, etc.
  984 + rows = self._get_rows(options)
  985 +
  986 + # Turn all data in all rows into Unicode, formatted as desired
  987 + formatted_rows = self._format_rows(rows, options)
  988 +
  989 + # Compute column widths
  990 + self._compute_widths(formatted_rows, options)
  991 +
  992 + # Add header or top of border
  993 + self._hrule = self._stringify_hrule(options)
  994 + if options["header"]:
  995 + lines.append(self._stringify_header(options))
  996 + elif options["border"] and options["hrules"] in (ALL, FRAME):
  997 + lines.append(self._hrule)
  998 +
  999 + # Add rows
  1000 + for row in formatted_rows:
  1001 + lines.append(self._stringify_row(row, options))
  1002 +
  1003 + # Add bottom of border
  1004 + if options["border"] and options["hrules"] == FRAME:
  1005 + lines.append(self._hrule)
  1006 +
  1007 + return self._unicode("\n").join(lines)
  1008 +
  1009 + def _stringify_hrule(self, options):
  1010 +
  1011 + if not options["border"]:
  1012 + return ""
  1013 + lpad, rpad = self._get_padding_widths(options)
  1014 + if options['vrules'] in (ALL, FRAME):
  1015 + bits = [options["junction_char"]]
  1016 + else:
  1017 + bits = [options["horizontal_char"]]
  1018 + # For tables with no data or fieldnames
  1019 + if not self._field_names:
  1020 + bits.append(options["junction_char"])
  1021 + return "".join(bits)
  1022 + for field, width in zip(self._field_names, self._widths):
  1023 + if options["fields"] and field not in options["fields"]:
  1024 + continue
  1025 + bits.append((width+lpad+rpad)*options["horizontal_char"])
  1026 + if options['vrules'] == ALL:
  1027 + bits.append(options["junction_char"])
  1028 + else:
  1029 + bits.append(options["horizontal_char"])
  1030 + if options["vrules"] == FRAME:
  1031 + bits.pop()
  1032 + bits.append(options["junction_char"])
  1033 + return "".join(bits)
  1034 +
  1035 + def _stringify_header(self, options):
  1036 +
  1037 + bits = []
  1038 + lpad, rpad = self._get_padding_widths(options)
  1039 + if options["border"]:
  1040 + if options["hrules"] in (ALL, FRAME):
  1041 + bits.append(self._hrule)
  1042 + bits.append("\n")
  1043 + if options["vrules"] in (ALL, FRAME):
  1044 + bits.append(options["vertical_char"])
  1045 + else:
  1046 + bits.append(" ")
  1047 + # For tables with no data or field names
  1048 + if not self._field_names:
  1049 + if options["vrules"] in (ALL, FRAME):
  1050 + bits.append(options["vertical_char"])
  1051 + else:
  1052 + bits.append(" ")
  1053 + for field, width, in zip(self._field_names, self._widths):
  1054 + if options["fields"] and field not in options["fields"]:
  1055 + continue
  1056 + if self._header_style == "cap":
  1057 + fieldname = field.capitalize()
  1058 + elif self._header_style == "title":
  1059 + fieldname = field.title()
  1060 + elif self._header_style == "upper":
  1061 + fieldname = field.upper()
  1062 + elif self._header_style == "lower":
  1063 + fieldname = field.lower()
  1064 + else:
  1065 + fieldname = field
  1066 + bits.append(" " * lpad + self._justify(fieldname, width, self._align[field]) + " " * rpad)
  1067 + if options["border"]:
  1068 + if options["vrules"] == ALL:
  1069 + bits.append(options["vertical_char"])
  1070 + else:
  1071 + bits.append(" ")
  1072 + # If vrules is FRAME, then we just appended a space at the end
  1073 + # of the last field, when we really want a vertical character
  1074 + if options["border"] and options["vrules"] == FRAME:
  1075 + bits.pop()
  1076 + bits.append(options["vertical_char"])
  1077 + if options["border"] and options["hrules"] != NONE:
  1078 + bits.append("\n")
  1079 + bits.append(self._hrule)
  1080 + return "".join(bits)
  1081 +
  1082 + def _stringify_row(self, row, options):
  1083 +
  1084 + for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths):
  1085 + # Enforce max widths
  1086 + lines = value.split("\n")
  1087 + new_lines = []
  1088 + for line in lines:
  1089 + if _str_block_width(line) > width:
  1090 + line = textwrap.fill(line, width)
  1091 + new_lines.append(line)
  1092 + lines = new_lines
  1093 + value = "\n".join(lines)
  1094 + row[index] = value
  1095 +
  1096 + row_height = 0
  1097 + for c in row:
  1098 + h = _get_size(c)[1]
  1099 + if h > row_height:
  1100 + row_height = h
  1101 +
  1102 + bits = []
  1103 + lpad, rpad = self._get_padding_widths(options)
  1104 + for y in range(0, row_height):
  1105 + bits.append([])
  1106 + if options["border"]:
  1107 + if options["vrules"] in (ALL, FRAME):
  1108 + bits[y].append(self.vertical_char)
  1109 + else:
  1110 + bits[y].append(" ")
  1111 +
  1112 + for field, value, width, in zip(self._field_names, row, self._widths):
  1113 +
  1114 + valign = self._valign[field]
  1115 + lines = value.split("\n")
  1116 + dHeight = row_height - len(lines)
  1117 + if dHeight:
  1118 + if valign == "m":
  1119 + lines = [""] * int(dHeight / 2) + lines + [""] * (dHeight - int(dHeight / 2))
  1120 + elif valign == "b":
  1121 + lines = [""] * dHeight + lines
  1122 + else:
  1123 + lines = lines + [""] * dHeight
  1124 +
  1125 + y = 0
  1126 + for l in lines:
  1127 + if options["fields"] and field not in options["fields"]:
  1128 + continue
  1129 +
  1130 + bits[y].append(" " * lpad + self._justify(l, width, self._align[field]) + " " * rpad)
  1131 + if options["border"]:
  1132 + if options["vrules"] == ALL:
  1133 + bits[y].append(self.vertical_char)
  1134 + else:
  1135 + bits[y].append(" ")
  1136 + y += 1
  1137 +
  1138 + # If vrules is FRAME, then we just appended a space at the end
  1139 + # of the last field, when we really want a vertical character
  1140 + for y in range(0, row_height):
  1141 + if options["border"] and options["vrules"] == FRAME:
  1142 + bits[y].pop()
  1143 + bits[y].append(options["vertical_char"])
  1144 +
  1145 + if options["border"] and options["hrules"]== ALL:
  1146 + bits[row_height-1].append("\n")
  1147 + bits[row_height-1].append(self._hrule)
  1148 +
  1149 + for y in range(0, row_height):
  1150 + bits[y] = "".join(bits[y])
  1151 +
  1152 + return "\n".join(bits)
  1153 +
  1154 + ##############################
  1155 + # HTML STRING METHODS #
  1156 + ##############################
  1157 +
  1158 + def get_html_string(self, **kwargs):
  1159 +
  1160 + """Return string representation of HTML formatted version of table in current state.
  1161 +
  1162 + Arguments:
  1163 +
  1164 + start - index of first data row to include in output
  1165 + end - index of last data row to include in output PLUS ONE (list slice style)
  1166 + fields - names of fields (columns) to include
  1167 + header - print a header showing field names (True or False)
  1168 + border - print a border around the table (True or False)
  1169 + hrules - controls printing of horizontal rules after rows. Allowed values: ALL, FRAME, HEADER, NONE
  1170 + vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
  1171 + int_format - controls formatting of integer data
  1172 + float_format - controls formatting of floating point data
  1173 + padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
  1174 + left_padding_width - number of spaces on left hand side of column data
  1175 + right_padding_width - number of spaces on right hand side of column data
  1176 + sortby - name of field to sort rows by
  1177 + sort_key - sorting key function, applied to data points before sorting
  1178 + attributes - dictionary of name/value pairs to include as HTML attributes in the <table> tag
  1179 + xhtml - print <br/> tags if True, <br> tags if false"""
  1180 +
  1181 + options = self._get_options(kwargs)
  1182 +
  1183 + if options["format"]:
  1184 + string = self._get_formatted_html_string(options)
  1185 + else:
  1186 + string = self._get_simple_html_string(options)
  1187 +
  1188 + return string
  1189 +
  1190 + def _get_simple_html_string(self, options):
  1191 +
  1192 + lines = []
  1193 + if options["xhtml"]:
  1194 + linebreak = "<br/>"
  1195 + else:
  1196 + linebreak = "<br>"
  1197 +
  1198 + open_tag = []
  1199 + open_tag.append("<table")
  1200 + if options["attributes"]:
  1201 + for attr_name in options["attributes"]:
  1202 + open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
  1203 + open_tag.append(">")
  1204 + lines.append("".join(open_tag))
  1205 +
  1206 + # Headers
  1207 + if options["header"]:
  1208 + lines.append(" <tr>")
  1209 + for field in self._field_names:
  1210 + if options["fields"] and field not in options["fields"]:
  1211 + continue
  1212 + lines.append(" <th>%s</th>" % escape(field).replace("\n", linebreak))
  1213 + lines.append(" </tr>")
  1214 +
  1215 + # Data
  1216 + rows = self._get_rows(options)
  1217 + formatted_rows = self._format_rows(rows, options)
  1218 + for row in formatted_rows:
  1219 + lines.append(" <tr>")
  1220 + for field, datum in zip(self._field_names, row):
  1221 + if options["fields"] and field not in options["fields"]:
  1222 + continue
  1223 + lines.append(" <td>%s</td>" % escape(datum).replace("\n", linebreak))
  1224 + lines.append(" </tr>")
  1225 +
  1226 + lines.append("</table>")
  1227 +
  1228 + return self._unicode("\n").join(lines)
  1229 +
  1230 + def _get_formatted_html_string(self, options):
  1231 +
  1232 + lines = []
  1233 + lpad, rpad = self._get_padding_widths(options)
  1234 + if options["xhtml"]:
  1235 + linebreak = "<br/>"
  1236 + else:
  1237 + linebreak = "<br>"
  1238 +
  1239 + open_tag = []
  1240 + open_tag.append("<table")
  1241 + if options["border"]:
  1242 + if options["hrules"] == ALL and options["vrules"] == ALL:
  1243 + open_tag.append(" frame=\"box\" rules=\"all\"")
  1244 + elif options["hrules"] == FRAME and options["vrules"] == FRAME:
  1245 + open_tag.append(" frame=\"box\"")
  1246 + elif options["hrules"] == FRAME and options["vrules"] == ALL:
  1247 + open_tag.append(" frame=\"box\" rules=\"cols\"")
  1248 + elif options["hrules"] == FRAME:
  1249 + open_tag.append(" frame=\"hsides\"")
  1250 + elif options["hrules"] == ALL:
  1251 + open_tag.append(" frame=\"hsides\" rules=\"rows\"")
  1252 + elif options["vrules"] == FRAME:
  1253 + open_tag.append(" frame=\"vsides\"")
  1254 + elif options["vrules"] == ALL:
  1255 + open_tag.append(" frame=\"vsides\" rules=\"cols\"")
  1256 + if options["attributes"]:
  1257 + for attr_name in options["attributes"]:
  1258 + open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
  1259 + open_tag.append(">")
  1260 + lines.append("".join(open_tag))
  1261 +
  1262 + # Headers
  1263 + if options["header"]:
  1264 + lines.append(" <tr>")
  1265 + for field in self._field_names:
  1266 + if options["fields"] and field not in options["fields"]:
  1267 + continue
  1268 + lines.append(" <th style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</th>" % (lpad, rpad, escape(field).replace("\n", linebreak)))
  1269 + lines.append(" </tr>")
  1270 +
  1271 + # Data
  1272 + rows = self._get_rows(options)
  1273 + formatted_rows = self._format_rows(rows, options)
  1274 + aligns = []
  1275 + valigns = []
  1276 + for field in self._field_names:
  1277 + aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]])
  1278 + valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]])
  1279 + for row in formatted_rows:
  1280 + lines.append(" <tr>")
  1281 + for field, datum, align, valign in zip(self._field_names, row, aligns, valigns):
  1282 + if options["fields"] and field not in options["fields"]:
  1283 + continue
  1284 + lines.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s\">%s</td>" % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
  1285 + lines.append(" </tr>")
  1286 + lines.append("</table>")
  1287 +
  1288 + return self._unicode("\n").join(lines)
  1289 +
  1290 +##############################
  1291 +# UNICODE WIDTH FUNCTIONS #
  1292 +##############################
  1293 +
  1294 +def _char_block_width(char):
  1295 + # Basic Latin, which is probably the most common case
  1296 + #if char in xrange(0x0021, 0x007e):
  1297 + #if char >= 0x0021 and char <= 0x007e:
  1298 + if 0x0021 <= char <= 0x007e:
  1299 + return 1
  1300 + # Chinese, Japanese, Korean (common)
  1301 + if 0x4e00 <= char <= 0x9fff:
  1302 + return 2
  1303 + # Hangul
  1304 + if 0xac00 <= char <= 0xd7af:
  1305 + return 2
  1306 + # Combining?
  1307 + if unicodedata.combining(uni_chr(char)):
  1308 + return 0
  1309 + # Hiragana and Katakana
  1310 + if 0x3040 <= char <= 0x309f or 0x30a0 <= char <= 0x30ff:
  1311 + return 2
  1312 + # Full-width Latin characters
  1313 + if 0xff01 <= char <= 0xff60:
  1314 + return 2
  1315 + # CJK punctuation
  1316 + if 0x3000 <= char <= 0x303e:
  1317 + return 2
  1318 + # Backspace and delete
  1319 + if char in (0x0008, 0x007f):
  1320 + return -1
  1321 + # Other control characters
  1322 + elif char in (0x0000, 0x001f):
  1323 + return 0
  1324 + # Take a guess
  1325 + return 1
  1326 +
  1327 +def _str_block_width(val):
  1328 +
  1329 + return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val))))
  1330 +
  1331 +##############################
  1332 +# TABLE FACTORIES #
  1333 +##############################
  1334 +
  1335 +def from_csv(fp, field_names = None, **kwargs):
  1336 +
  1337 + dialect = csv.Sniffer().sniff(fp.read(1024))
  1338 + fp.seek(0)
  1339 + reader = csv.reader(fp, dialect)
  1340 +
  1341 + table = PrettyTable(**kwargs)
  1342 + if field_names:
  1343 + table.field_names = field_names
  1344 + else:
  1345 + if py3k:
  1346 + table.field_names = [x.strip() for x in next(reader)]
  1347 + else:
  1348 + table.field_names = [x.strip() for x in reader.next()]
  1349 +
  1350 + for row in reader:
  1351 + table.add_row([x.strip() for x in row])
  1352 +
  1353 + return table
  1354 +
  1355 +def from_db_cursor(cursor, **kwargs):
  1356 +
  1357 + if cursor.description:
  1358 + table = PrettyTable(**kwargs)
  1359 + table.field_names = [col[0] for col in cursor.description]
  1360 + for row in cursor.fetchall():
  1361 + table.add_row(row)
  1362 + return table
  1363 +
  1364 +class TableHandler(HTMLParser):
  1365 +
  1366 + def __init__(self, **kwargs):
  1367 + HTMLParser.__init__(self)
  1368 + self.kwargs = kwargs
  1369 + self.tables = []
  1370 + self.last_row = []
  1371 + self.rows = []
  1372 + self.max_row_width = 0
  1373 + self.active = None
  1374 + self.last_content = ""
  1375 + self.is_last_row_header = False
  1376 +
  1377 + def handle_starttag(self,tag, attrs):
  1378 + self.active = tag
  1379 + if tag == "th":
  1380 + self.is_last_row_header = True
  1381 +
  1382 + def handle_endtag(self,tag):
  1383 + if tag in ["th", "td"]:
  1384 + stripped_content = self.last_content.strip()
  1385 + self.last_row.append(stripped_content)
  1386 + if tag == "tr":
  1387 + self.rows.append(
  1388 + (self.last_row, self.is_last_row_header))
  1389 + self.max_row_width = max(self.max_row_width, len(self.last_row))
  1390 + self.last_row = []
  1391 + self.is_last_row_header = False
  1392 + if tag == "table":
  1393 + table = self.generate_table(self.rows)
  1394 + self.tables.append(table)
  1395 + self.rows = []
  1396 + self.last_content = " "
  1397 + self.active = None
  1398 +
  1399 +
  1400 + def handle_data(self, data):
  1401 + self.last_content += data
  1402 +
  1403 + def generate_table(self, rows):
  1404 + """
  1405 + Generates from a list of rows a PrettyTable object.
  1406 + """
  1407 + table = PrettyTable(**self.kwargs)
  1408 + for row in self.rows:
  1409 + if len(row[0]) < self.max_row_width:
  1410 + appends = self.max_row_width - len(row[0])
  1411 + for i in range(1,appends):
  1412 + row[0].append("-")
  1413 +
  1414 + if row[1] == True:
  1415 + self.make_fields_unique(row[0])
  1416 + table.field_names = row[0]
  1417 + else:
  1418 + table.add_row(row[0])
  1419 + return table
  1420 +
  1421 + def make_fields_unique(self, fields):
  1422 + """
  1423 + iterates over the row and make each field unique
  1424 + """
  1425 + for i in range(0, len(fields)):
  1426 + for j in range(i+1, len(fields)):
  1427 + if fields[i] == fields[j]:
  1428 + fields[j] += "'"
  1429 +
  1430 +def from_html(html_code, **kwargs):
  1431 + """
  1432 + Generates a list of PrettyTables from a string of HTML code. Each <table> in
  1433 + the HTML becomes one PrettyTable object.
  1434 + """
  1435 +
  1436 + parser = TableHandler(**kwargs)
  1437 + parser.feed(html_code)
  1438 + return parser.tables
  1439 +
  1440 +def from_html_one(html_code, **kwargs):
  1441 + """
  1442 + Generates a PrettyTables from a string of HTML code which contains only a
  1443 + single <table>
  1444 + """
  1445 +
  1446 + tables = from_html(html_code, **kwargs)
  1447 + try:
  1448 + assert len(tables) == 1
  1449 + except AssertionError:
  1450 + raise Exception("More than one <table> in provided HTML code! Use from_html instead.")
  1451 + return tables[0]
  1452 +
  1453 +##############################
  1454 +# MAIN (TEST FUNCTION) #
  1455 +##############################
  1456 +
  1457 +def main():
  1458 +
  1459 + x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
  1460 + x.sortby = "Population"
  1461 + x.reversesort = True
  1462 + x.int_format["Area"] = "04d"
  1463 + x.float_format = "6.1f"
  1464 + x.align["City name"] = "l" # Left align city names
  1465 + x.add_row(["Adelaide", 1295, 1158259, 600.5])
  1466 + x.add_row(["Brisbane", 5905, 1857594, 1146.4])
  1467 + x.add_row(["Darwin", 112, 120900, 1714.7])
  1468 + x.add_row(["Hobart", 1357, 205556, 619.5])
  1469 + x.add_row(["Sydney", 2058, 4336374, 1214.8])
  1470 + x.add_row(["Melbourne", 1566, 3806092, 646.9])
  1471 + x.add_row(["Perth", 5386, 1554769, 869.4])
  1472 + print(x)
  1473 +
  1474 +if __name__ == "__main__":
  1475 + main()
... ...