diff --git a/oletools/oleid.py b/oletools/oleid.py
index 39354de..2b977fb 100644
--- a/oletools/oleid.py
+++ b/oletools/oleid.py
@@ -47,8 +47,9 @@ http://www.decalage.info/python/oletools
# 2012-10-29 v0.01 PL: - first version
# 2014-11-29 v0.02 PL: - use olefile instead of OleFileIO_PL
# - improved usage display with -h
+# 2014-11-30 v0.03 PL: - improved output with prettytable
-__version__ = '0.02'
+__version__ = '0.03'
#------------------------------------------------------------------------------
@@ -71,6 +72,7 @@ __version__ = '0.02'
import optparse, sys, os, re, zlib, struct
import thirdparty.olefile as olefile
+from thirdparty.prettytable import prettytable
#=== FUNCTIONS ===============================================================
@@ -276,8 +278,17 @@ def main():
print '\nFilename:', filename
oleid = OleID(filename)
indicators = oleid.check()
+
+ t = prettytable.PrettyTable(['Indicator', 'Value'])
+ t.align = 'l'
+ t.max_width = 39
+ #t.border = False
+
for indicator in indicators:
- print '%s: %s' % (indicator.name, indicator.value)
+ #print '%s: %s' % (indicator.name, indicator.value)
+ t.add_row((indicator.name, indicator.value))
+
+ print t
if __name__ == '__main__':
main()
diff --git a/oletools/oletimes.py b/oletools/oletimes.py
index ba16a66..67e4841 100644
--- a/oletools/oletimes.py
+++ b/oletools/oletimes.py
@@ -45,8 +45,9 @@ http://www.decalage.info/python/oletools
# 2013-07-24 v0.01 PL: - first version
# 2014-11-29 v0.02 PL: - use olefile instead of OleFileIO_PL
# - improved usage display
+# 2014-11-30 v0.03 PL: - improved output with prettytable
-__version__ = '0.02'
+__version__ = '0.03'
#------------------------------------------------------------------------------
# TODO:
@@ -57,8 +58,9 @@ __version__ = '0.02'
#=== IMPORTS =================================================================
-import sys
+import sys, datetime
import thirdparty.olefile as olefile
+from thirdparty.prettytable import prettytable
#=== MAIN =================================================================
@@ -68,9 +70,30 @@ try:
except IndexError:
sys.exit(__doc__)
-print'- Root mtime=%s ctime=%s' % (ole.root.getmtime(), ole.root.getctime())
+def dt2str (dt):
+ """
+ Convert a datetime object to a string for display, without microseconds
+
+ :param dt: datetime.datetime object, or None
+ :return: str, or None
+ """
+ if dt is None:
+ return None
+ dt = dt.replace(microsecond = 0)
+ return str(dt)
+
+t = prettytable.PrettyTable(['Stream/Storage name', 'Modification Time', 'Creation Time'])
+t.align = 'l'
+t.max_width = 26
+#t.border = False
+
+#print'- Root mtime=%s ctime=%s' % (ole.root.getmtime(), ole.root.getctime())
+t.add_row(('Root', dt2str(ole.root.getmtime()), dt2str(ole.root.getctime())))
for obj in ole.listdir(streams=True, storages=True):
- print '- %s: mtime=%s ctime=%s' % (repr('/'.join(obj)), ole.getmtime(obj), ole.getctime(obj))
+ #print '- %s: mtime=%s ctime=%s' % (repr('/'.join(obj)), ole.getmtime(obj), ole.getctime(obj))
+ t.add_row((repr('/'.join(obj)), dt2str(ole.getmtime(obj)), dt2str(ole.getctime(obj))))
+
+print t
ole.close()
diff --git a/oletools/thirdparty/prettytable/CHANGELOG b/oletools/thirdparty/prettytable/CHANGELOG
new file mode 100644
index 0000000..a790f1f
--- /dev/null
+++ b/oletools/thirdparty/prettytable/CHANGELOG
@@ -0,0 +1,142 @@
+########## PrettyTable 0.7 - Feb 17, 2013 ###########
+
+* Improved Python 2 and 3 compatibility (2.4-3.2).
+* Improved support for non-Latin characters. Table widths should
+ now be calculated correctly for tables with e.g. Japanese text.
+* Table contents can now be read in from a .csv file
+* Table contents can now be read in from a DB-API compatible cursor
+* Table contents can now be read in from a string containing a
+ HTML table (thanks to Christoph Robbert for submitting this patch!)
+* new valign attribute controls vertical alignment of text when
+ some cells in a row have multiple lines of text and others don't.
+ (thanks to Google Code user maartendb for submitting this patch!)
+* hrules attribute can now be set to HEADER, which draws a rule only
+ under the header row
+* new vrules attribute controls drawing of vertical rules and can
+ be set to FRAME, ALL or NONE
+* new header_style attribute controls formatting of text in table
+ headers and can be set to "cap", "title", "upper", "lower" or None
+* Fixed a simple bug regarding validation of max_width (thanks to
+ Anthony Toole for pointing out this bug and providing a patch).
+* Fixed a simple bug regarding initialisation of int_format value
+ for new tables (thanks to Ingo Schmiegel for pointing out this
+ bug!)
+* Fixed a bug regarding some constructor keywords, such as "border",
+ being ignored (thanks to Google Code user antonio.s.messina for
+ reporting this bug).
+
+########## PrettyTable 0.6 - May 5, 2012 ##########
+
+* Code is now simultaneously compatible with Python 2 and 3
+* Replaced all setter methods with managed attributes
+* All styling options can now be set persistently as managed attributes
+* Added "add_style" method to make setting style options easily
+* Added "del_row", "clear_rows" and "clear" methods to facilitate
+ removal of data from table.
+* Added "copy" method to facilitate cloning of a table.
+* Removed caching functionality, which added complexity and fragility
+ for relatively little gain
+* Removed methods that just printed strings produced by get_string and
+ get_html_string - just use inbuilt print!
+* Improved unicode support (thanks to Google Code user ru.w31rd0 for
+ patch!)
+* Added support for decimal and floating point number formatting
+ support (thanks to Google Code user willfurnass for the suggestion!)
+* Added support for using a custom key sorting methods (thanks to
+ Google Code user amannijhawan for the suggestion!)
+* Added support for line breaks in data (suggested and implemented by
+ Klein Stephane)
+* Added support for max column widths (thanks to Tibor Arpas for the
+ suggestion!)
+* Fixed table slicing
+* Fixed bug where closing
tags in HTML tables were not printed
+ (thanks to Google Code user kehander for reporting this bug!)
+* Fixed HTML table sorting bug (thanks to Google Code user dougbeal
+ for reporting this bug!)
+* Fixed bug whereby changing field_names did not recompute widths
+ (thanks to Google Code user denilsonsa for reporting this bug!)
+
+########## PrettyTable 0.5 - May 26, 2009 ##########
+
+* Fixed a bug whereby printing with headers=False and border=False
+ would introduce an extraneous newline. Thanks to Alexander Lamaison
+ for reporting this bug.
+* When printing with headers=False, column widths will now be reduced
+ as appropriate in columns where the field name is wider than the
+ data. Thanks to Alexander Lamaison for suggesting this behaviour.
+* Support for Unicode has improved. Thanks to Chris Clark for
+ submitting this improvement.
+* The value of the "border" argument now correctly controls the
+ presence of a border when printing HTML tables with print_html or
+ get_html_string, instead of being incorrectly ignored. Thanks to
+ Chris Clark for fixing this.
+* The print_html and get_html_string methods now accept an
+ "attributes" argument which is a dictionary of name/value pairs to be
+ placed inside the tag (so you can, e.g. set class, name or id
+ values in order to style your table with CSS). Thanks to Chris Clark
+ for submitting this feature.
+* The print_html and get_html_string methods now, by default, do their
+ best to match the various formatting options in their HTML output.
+ They use inline CSS to adjust the alignment of data in columns, the
+ padding widths of columns and in some cases the border settings. You
+ can give either method a "format=False" attribute to turn this
+ behaviour off if you want to do your own styling. With "format=False"
+ the methods print a "bare bones" table, similar to the default
+ behaviour in 0.4.
+
+########## PrettyTable 0.4 - May 13, 2009 ##########
+
+* Added "add_column" method to enable building tables up column-by-column.
+* Added "print_HTML" and "get_HTML_string" methods to enable HTML table
+ production.
+* Added "set_border_chars" method to enable control over characters used to
+ draw the table border.
+* Added "set_left_padding" and "set_right_padding" methods to allow
+ independent padding control for both sides of a column.
+* Added "sortby" option to enable column sorting.
+* Added "header" option to enable switching off field name printing at top of
+ table.
+* Modified "hrules" option to enable greater control over presence of
+ horizontal lines.
+* Added "border" option to enable switching off all line printing.
+
+Thanks to Tim Cera, Chris Clark, Alexander Lamaison for suggesting and helping
+to test many of the new features in this release.
+
+########## PrettyTable 0.3 - May 01, 2009 ##########
+
+* Added "padding_width" option to control the number of spaces between the
+ vertical line rules at the edges of a column and its content. This can be
+ set as a keyword argument to the constructor or after instantiation using
+ the "set_padding_width" method. The value is set to 1 by defaut. If your
+ table is too wide for a small screen with this value, setting it to 0 might
+ help you squeeze it in.
+
+Thanks to Chris Clark for contributing a patch against 0.2.1 to add this
+feature!
+
+########## PrettyTable 0.2.1 - April 29, 2009 ##########
+
+* Caching no longer breaks when using the "printt(fields=[...])" syntax. The
+ list of fields was not hashable and hence could not be used as a dictionary
+ key. I fixed this using the output of the "cPickle" module's "dumps"
+ function as the dictionary key instead.
+* Horizontal lines are now the appropriate length when the above syntax is
+ used.
+
+Thanks to Julien Koesten for reporting these bugs and testing the fixes almost
+immediately after the release of 0.2!
+
+########## PrettyTable 0.2 - April 29, 2009 ##########
+
+* Added "get_string" method.
+* Added "__str__" method (which just calls "get_string") to enable nice
+ "print x" syntax.
+* Can now pass field names as a constructor argument.
+* Return values of "get_string" are cached in a dictionary that is only
+ cleared after a call to "add_row" or something else which invalidates the
+ cache.
+
+########## PrettyTable 0.1 - February 26, 2009 #########
+
+* Original release
diff --git a/oletools/thirdparty/prettytable/COPYING b/oletools/thirdparty/prettytable/COPYING
new file mode 100644
index 0000000..7de41fb
--- /dev/null
+++ b/oletools/thirdparty/prettytable/COPYING
@@ -0,0 +1,30 @@
+# Copyright (c) 2009-2013 Luke Maurits
+# All rights reserved.
+# With contributions from:
+# * Chris Clark
+# * Christoph Robbert
+# * Klein Stephane
+# * "maartendb"
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
diff --git a/oletools/thirdparty/prettytable/README b/oletools/thirdparty/prettytable/README
new file mode 100644
index 0000000..5f85d05
--- /dev/null
+++ b/oletools/thirdparty/prettytable/README
@@ -0,0 +1,498 @@
+TUTORIAL ON HOW TO USE THE PRETTYTABLE 0.6+ API
+
+*** This tutorial is distributed with PrettyTable and is meant to serve
+as a "quick start" guide for the lazy or impatient. It is not an
+exhaustive description of the whole API, and it is not guaranteed to be
+100% up to date. For more complete and update documentation, check the
+PrettyTable wiki at http://code.google.com/p/prettytable/w/list ***
+
+= Getting your data into (and out of) the table =
+
+Let's suppose you have a shiny new PrettyTable:
+
+from prettytable import PrettyTable
+x = PrettyTable()
+
+and you want to put some data into it. You have a few options.
+
+== Row by row ==
+
+You can add data one row at a time. To do this you can set the field names
+first using the `field_names` attribute, and then add the rows one at a time
+using the `add_row` method:
+
+x.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
+x.add_row(["Adelaide",1295, 1158259, 600.5])
+x.add_row(["Brisbane",5905, 1857594, 1146.4])
+x.add_row(["Darwin", 112, 120900, 1714.7])
+x.add_row(["Hobart", 1357, 205556, 619.5])
+x.add_row(["Sydney", 2058, 4336374, 1214.8])
+x.add_row(["Melbourne", 1566, 3806092, 646.9])
+x.add_row(["Perth", 5386, 1554769, 869.4])
+
+== Column by column ==
+
+You can add data one column at a time as well. To do this you use the
+`add_column` method, which takes two arguments - a string which is the name for
+the field the column you are adding corresponds to, and a list or tuple which
+contains the column data"
+
+x.add_column("City name",
+["Adelaide","Brisbane","Darwin","Hobart","Sydney","Melbourne","Perth"])
+x.add_column("Area", [1295, 5905, 112, 1357, 2058, 1566, 5386])
+x.add_column("Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092,
+1554769])
+x.add_column("Annual Rainfall",[600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9,
+869.4])
+
+== Mixing and matching ==
+
+If you really want to, you can even mix and match `add_row` and `add_column`
+and build some of your table in one way and some of it in the other. There's a
+unit test which makes sure that doing things this way will always work out
+nicely as if you'd done it using just one of the two approaches. Tables built
+this way are kind of confusing for other people to read, though, so don't do
+this unless you have a good reason.
+
+== Importing data from a CSV file ==
+
+If you have your table data in a comma separated values file (.csv), you can
+read this data into a PrettyTable like this:
+
+from prettytable import from_csv
+fp = open("myfile.csv", "r")
+mytable = from_csv(fp)
+fp.close()
+
+== Importing data from a HTML string ==
+
+If you have a string containing a HTML , you can read this data into a
+PrettyTable like this:
+
+from prettytable import from_html
+mytable = from_html(html_string)
+
+== Importing data from a database cursor ==
+
+If you have your table data in a database which you can access using a library
+which confirms to the Python DB-API (e.g. an SQLite database accessible using
+the sqlite module), then you can build a PrettyTable using a cursor object,
+like this:
+
+import sqlite3
+from prettytable import from_db_cursor
+
+connection = sqlite3.connect("mydb.db")
+cursor = connection.cursor()
+cursor.execute("SELECT field1, field2, field3 FROM my_table")
+mytable = from_db_cursor(cursor)
+
+== Getting data out ==
+
+There are three ways to get data out of a PrettyTable, in increasing order of
+completeness:
+
+ * The `del_row` method takes an integer index of a single row to delete.
+ * The `clear_rows` method takes no arguments and deletes all the rows in the
+table - but keeps the field names as they were so you that you can repopulate
+it with the same kind of data.
+ * The `clear` method takes no arguments and deletes all rows and all field
+names. It's not quite the same as creating a fresh table instance, though -
+style related settings, discussed later, are maintained.
+
+= Displaying your table in ASCII form =
+
+PrettyTable's main goal is to let you print tables in an attractive ASCII form,
+like this:
+
++-----------+------+------------+-----------------+
+| City name | Area | Population | Annual Rainfall |
++-----------+------+------------+-----------------+
+| Adelaide | 1295 | 1158259 | 600.5 |
+| Brisbane | 5905 | 1857594 | 1146.4 |
+| Darwin | 112 | 120900 | 1714.7 |
+| Hobart | 1357 | 205556 | 619.5 |
+| Melbourne | 1566 | 3806092 | 646.9 |
+| Perth | 5386 | 1554769 | 869.4 |
+| Sydney | 2058 | 4336374 | 1214.8 |
++-----------+------+------------+-----------------+
+
+You can print tables like this to `stdout` or get string representations of
+them.
+
+== Printing ==
+
+To print a table in ASCII form, you can just do this:
+
+print x
+
+in Python 2.x or:
+
+print(x)
+
+in Python 3.x.
+
+The old x.printt() method from versions 0.5 and earlier has been removed.
+
+To pass options changing the look of the table, use the get_string() method
+documented below:
+
+print x.get_string()
+
+== Stringing ==
+
+If you don't want to actually print your table in ASCII form but just get a
+string containing what _would_ be printed if you use "print x", you can use
+the `get_string` method:
+
+mystring = x.get_string()
+
+This string is guaranteed to look exactly the same as what would be printed by
+doing "print x". You can now do all the usual things you can do with a
+string, like write your table to a file or insert it into a GUI.
+
+== Controlling which data gets displayed ==
+
+If you like, you can restrict the output of `print x` or `x.get_string` to
+only the fields or rows you like.
+
+The `fields` argument to these methods takes a list of field names to be
+printed:
+
+print x.get_string(fields=["City name", "Population"])
+
+gives:
+
++-----------+------------+
+| City name | Population |
++-----------+------------+
+| Adelaide | 1158259 |
+| Brisbane | 1857594 |
+| Darwin | 120900 |
+| Hobart | 205556 |
+| Melbourne | 3806092 |
+| Perth | 1554769 |
+| Sydney | 4336374 |
++-----------+------------+
+
+The `start` and `end` arguments take the index of the first and last row to
+print respectively. Note that the indexing works like Python list slicing - to
+print the 2nd, 3rd and 4th rows of the table, set `start` to 1 (the first row
+is row 0, so the second is row 1) and set `end` to 4 (the index of the 4th row,
+plus 1):
+
+print x.get_string(start=1,end=4)
+
+prints:
+
++-----------+------+------------+-----------------+
+| City name | Area | Population | Annual Rainfall |
++-----------+------+------------+-----------------+
+| Brisbane | 5905 | 1857594 | 1146.4 |
+| Darwin | 112 | 120900 | 1714.7 |
+| Hobart | 1357 | 205556 | 619.5 |
++-----------+------+------------+-----------------+
+
+== Changing the alignment of columns ==
+
+By default, all columns in a table are centre aligned.
+
+=== All columns at once ===
+
+You can change the alignment of all the columns in a table at once by assigning
+a one character string to the `align` attribute. The allowed strings are "l",
+"r" and "c" for left, right and centre alignment, respectively:
+
+x.align = "r"
+print x
+
+gives:
+
++-----------+------+------------+-----------------+
+| City name | Area | Population | Annual Rainfall |
++-----------+------+------------+-----------------+
+| Adelaide | 1295 | 1158259 | 600.5 |
+| Brisbane | 5905 | 1857594 | 1146.4 |
+| Darwin | 112 | 120900 | 1714.7 |
+| Hobart | 1357 | 205556 | 619.5 |
+| Melbourne | 1566 | 3806092 | 646.9 |
+| Perth | 5386 | 1554769 | 869.4 |
+| Sydney | 2058 | 4336374 | 1214.8 |
++-----------+------+------------+-----------------+
+
+=== One column at a time ===
+
+You can also change the alignment of individual columns based on the
+corresponding field name by treating the `align` attribute as if it were a
+dictionary.
+
+x.align["City name"] = "l"
+x.align["Area"] = "c"
+x.align["Population"] = "r"
+x.align["Annual Rainfall"] = "c"
+print x
+
+gives:
+
++-----------+------+------------+-----------------+
+| City name | Area | Population | Annual Rainfall |
++-----------+------+------------+-----------------+
+| Adelaide | 1295 | 1158259 | 600.5 |
+| Brisbane | 5905 | 1857594 | 1146.4 |
+| Darwin | 112 | 120900 | 1714.7 |
+| Hobart | 1357 | 205556 | 619.5 |
+| Melbourne | 1566 | 3806092 | 646.9 |
+| Perth | 5386 | 1554769 | 869.4 |
+| Sydney | 2058 | 4336374 | 1214.8 |
++-----------+------+------------+-----------------+
+
+== Sorting your table by a field ==
+
+You can make sure that your ASCII tables are produced with the data sorted by
+one particular field by giving `get_string` a `sortby` keyword argument, which
+ must be a string containing the name of one field.
+
+For example, to print the example table we built earlier of Australian capital
+city data, so that the most populated city comes last, we can do this:
+
+print x.get_string(sortby="Population")
+
+to get
+
++-----------+------+------------+-----------------+
+| City name | Area | Population | Annual Rainfall |
++-----------+------+------------+-----------------+
+| Darwin | 112 | 120900 | 1714.7 |
+| Hobart | 1357 | 205556 | 619.5 |
+| Adelaide | 1295 | 1158259 | 600.5 |
+| Perth | 5386 | 1554769 | 869.4 |
+| Brisbane | 5905 | 1857594 | 1146.4 |
+| Melbourne | 1566 | 3806092 | 646.9 |
+| Sydney | 2058 | 4336374 | 1214.8 |
++-----------+------+------------+-----------------+
+
+If we want the most populated city to come _first_, we can also give a
+`reversesort=True` argument.
+
+If you _always_ want your tables to be sorted in a certain way, you can make
+the setting long term like this:
+
+x.sortby = "Population"
+print x
+print x
+print x
+
+All three tables printed by this code will be sorted by population (you could
+do `x.reversesort = True` as well, if you wanted). The behaviour will persist
+until you turn it off:
+
+x.sortby = None
+
+If you want to specify a custom sorting function, you can use the `sort_key`
+keyword argument. Pass this a function which accepts two lists of values
+and returns a negative or positive value depending on whether the first list
+should appeare before or after the second one. If your table has n columns,
+each list will have n+1 elements. Each list corresponds to one row of the
+table. The first element will be whatever data is in the relevant row, in
+the column specified by the `sort_by` argument. The remaining n elements
+are the data in each of the table's columns, in order, including a repeated
+instance of the data in the `sort_by` column.
+
+= Changing the appearance of your table - the easy way =
+
+By default, PrettyTable produces ASCII tables that look like the ones used in
+SQL database shells. But if can print them in a variety of other formats as
+well. If the format you want to use is common, PrettyTable makes this very
+easy for you to do using the `set_style` method. If you want to produce an
+uncommon table, you'll have to do things slightly harder (see later).
+
+== Setting a table style ==
+
+You can set the style for your table using the `set_style` method before any
+calls to `print` or `get_string`. Here's how to print a table in a format
+which works nicely with Microsoft Word's "Convert to table" feature:
+
+from prettytable import MSWORD_FRIENDLY
+x.set_style(MSWORD_FRIENDLY)
+print x
+
+In addition to `MSWORD_FRIENDLY` there are currently two other in-built styles
+you can use for your tables:
+
+ * `DEFAULT` - The default look, used to undo any style changes you may have
+made
+ * `PLAIN_COLUMN` - A borderless style that works well with command line
+programs for columnar data
+
+Other styles are likely to appear in future releases.
+
+= Changing the appearance of your table - the hard way =
+
+If you want to display your table in a style other than one of the in-built
+styles listed above, you'll have to set things up the hard way.
+
+Don't worry, it's not really that hard!
+
+== Style options ==
+
+PrettyTable has a number of style options which control various aspects of how
+tables are displayed. You have the freedom to set each of these options
+individually to whatever you prefer. The `set_style` method just does this
+automatically for you.
+
+The options are these:
+
+ * `border` - A boolean option (must be `True` or `False`). Controls whether
+ or not a border is drawn around the table.
+ * `header` - A boolean option (must be `True` or `False`). Controls whether
+ or not the first row of the table is a header showing the names of all the
+ fields.
+ * `hrules` - Controls printing of horizontal rules after rows. Allowed
+ values: FRAME, HEADER, ALL, NONE - note that these are variables defined
+ inside the `prettytable` module so make sure you import them or use
+ `prettytable.FRAME` etc.
+ * `vrules` - Controls printing of vertical rules between columns. Allowed
+ values: FRAME, ALL, NONE.
+ * `int_format` - A string which controls the way integer data is printed.
+ This works like: print "%d" % data
+ * `float_format` - A string which controls the way floating point data is
+ printed. This works like: print "%f" % data
+ * `padding_width` - Number of spaces on either side of column data (only used
+ if left and right paddings are None).
+ * `left_padding_width` - Number of spaces on left hand side of column data.
+ * `right_padding_width` - Number of spaces on right hand side of column data.
+ * `vertical_char` - Single character string used to draw vertical lines.
+ Default is `|`.
+ * `horizontal_char` - Single character string used to draw horizontal lines.
+ Default is `-`.
+ * `junction_char` - Single character string used to draw line junctions.
+ Default is `+`.
+
+You can set the style options to your own settings in two ways:
+
+== Setting style options for the long term ==
+
+If you want to print your table with a different style several times, you can
+set your option for the "long term" just by changing the appropriate
+attributes. If you never want your tables to have borders you can do this:
+
+x.border = False
+print x
+print x
+print x
+
+Neither of the 3 tables printed by this will have borders, even if you do
+things like add extra rows inbetween them. The lack of borders will last until
+you do:
+
+x.border = True
+
+to turn them on again. This sort of long term setting is exactly how
+`set_style` works. `set_style` just sets a bunch of attributes to pre-set
+values for you.
+
+Note that if you know what style options you want at the moment you are
+creating your table, you can specify them using keyword arguments to the
+constructor. For example, the following two code blocks are equivalent:
+
+x = PrettyTable()
+x.border = False
+x.header = False
+x.padding_width = 5
+
+x = PrettyTable(border=False, header=False, padding_width=5)
+
+== Changing style options just once ==
+
+If you don't want to make long term style changes by changing an attribute like
+in the previous section, you can make changes that last for just one
+``get_string`` by giving those methods keyword arguments. To print two
+"normal" tables with one borderless table between them, you could do this:
+
+print x
+print x.get_string(border=False)
+print x
+
+= Displaying your table in HTML form =
+
+PrettyTable will also print your tables in HTML form, as ``s. Just like
+in ASCII form, you can actually print your table - just use `print_html()` - or
+get a string representation - just use `get_html_string()`. HTML printing
+supports the `fields`, `start`, `end`, `sortby` and `reversesort` arguments in
+exactly the same way as ASCII printing.
+
+== Styling HTML tables ==
+
+By default, PrettyTable outputs HTML for "vanilla" tables. The HTML code is
+quite simple. It looks like this:
+
+
+
+ | City name |
+ Area |
+ Population |
+ Annual Rainfall |
+
+
+ | Adelaide |
+ 1295 |
+ 1158259 |
+ 600.5 |
+
+ | Brisbane |
+ 5905 |
+ 1857594 |
+ 1146.4 |
+ ...
+ ...
+ ...
+
+
+If you like, you can ask PrettyTable to do its best to mimick the style options
+that your table has set using inline CSS. This is done by giving a
+`format=True` keyword argument to either the `print_html` or `get_html_string`
+methods. Note that if you _always_ want to print formatted HTML you can do:
+
+x.format = True
+
+and the setting will persist until you turn it off.
+
+Just like with ASCII tables, if you want to change the table's style for just
+one `print_html` or one `get_html_string` you can pass those methods keyword
+arguments - exactly like `print` and `get_string`.
+
+== Setting HTML attributes ==
+
+You can provide a dictionary of HTML attribute name/value pairs to the
+`print_html` and `get_html_string` methods using the `attributes` keyword
+argument. This lets you specify common HTML attributes like `name`, `id` and
+`class` that can be used for linking to your tables or customising their
+appearance using CSS. For example:
+
+x.print_html(attributes={"name":"my_table", "class":"red_table"})
+
+will print:
+
+
+
+ | City name |
+ Area |
+ Population |
+ Annual Rainfall |
+
+ ...
+ ...
+ ...
+
+
+= Miscellaneous things =
+
+== Copying a table ==
+
+You can call the `copy` method on a PrettyTable object without arguments to
+return an identical independent copy of the table.
+
+If you want a copy of a PrettyTable object with just a subset of the rows,
+you can use list slicing notation:
+
+new_table = old_table[0:5]
diff --git a/oletools/thirdparty/prettytable/__init__.py b/oletools/thirdparty/prettytable/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/oletools/thirdparty/prettytable/__init__.py
diff --git a/oletools/thirdparty/prettytable/prettytable.py b/oletools/thirdparty/prettytable/prettytable.py
new file mode 100644
index 0000000..8abb952
--- /dev/null
+++ b/oletools/thirdparty/prettytable/prettytable.py
@@ -0,0 +1,1475 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2009-2013, Luke Maurits
+# All rights reserved.
+# With contributions from:
+# * Chris Clark
+# * Klein Stephane
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+__version__ = "0.7.2"
+
+import copy
+import csv
+import random
+import re
+import sys
+import textwrap
+import itertools
+import unicodedata
+
+py3k = sys.version_info[0] >= 3
+if py3k:
+ unicode = str
+ basestring = str
+ itermap = map
+ iterzip = zip
+ uni_chr = chr
+ from html.parser import HTMLParser
+else:
+ itermap = itertools.imap
+ iterzip = itertools.izip
+ uni_chr = unichr
+ from HTMLParser import HTMLParser
+
+if py3k and sys.version_info[1] >= 2:
+ from html import escape
+else:
+ from cgi import escape
+
+# hrule styles
+FRAME = 0
+ALL = 1
+NONE = 2
+HEADER = 3
+
+# Table styles
+DEFAULT = 10
+MSWORD_FRIENDLY = 11
+PLAIN_COLUMNS = 12
+RANDOM = 20
+
+_re = re.compile("\033\[[0-9;]*m")
+
+def _get_size(text):
+ lines = text.split("\n")
+ height = len(lines)
+ width = max([_str_block_width(line) for line in lines])
+ return (width, height)
+
+class PrettyTable(object):
+
+ def __init__(self, field_names=None, **kwargs):
+
+ """Return a new PrettyTable instance
+
+ Arguments:
+
+ encoding - Unicode encoding scheme used to decode any encoded input
+ field_names - list or tuple of field names
+ fields - list or tuple of field names to include in displays
+ start - index of first data row to include in output
+ end - index of last data row to include in output PLUS ONE (list slice style)
+ header - print a header showing field names (True or False)
+ header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)
+ border - print a border around the table (True or False)
+ hrules - controls printing of horizontal rules after rows. Allowed values: FRAME, HEADER, ALL, NONE
+ vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
+ int_format - controls formatting of integer data
+ float_format - controls formatting of floating point data
+ padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
+ left_padding_width - number of spaces on left hand side of column data
+ right_padding_width - number of spaces on right hand side of column data
+ vertical_char - single character string used to draw vertical lines
+ horizontal_char - single character string used to draw horizontal lines
+ junction_char - single character string used to draw line junctions
+ sortby - name of field to sort rows by
+ sort_key - sorting key function, applied to data points before sorting
+ valign - default valign for each row (None, "t", "m" or "b")
+ reversesort - True or False to sort in descending or ascending order"""
+
+ self.encoding = kwargs.get("encoding", "UTF-8")
+
+ # Data
+ self._field_names = []
+ self._align = {}
+ self._valign = {}
+ self._max_width = {}
+ self._rows = []
+ if field_names:
+ self.field_names = field_names
+ else:
+ self._widths = []
+
+ # Options
+ self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split()
+ self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split())
+ self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split())
+ for option in self._options:
+ if option in kwargs:
+ self._validate_option(option, kwargs[option])
+ else:
+ kwargs[option] = None
+
+ self._start = kwargs["start"] or 0
+ self._end = kwargs["end"] or None
+ self._fields = kwargs["fields"] or None
+
+ if kwargs["header"] in (True, False):
+ self._header = kwargs["header"]
+ else:
+ self._header = True
+ self._header_style = kwargs["header_style"] or None
+ if kwargs["border"] in (True, False):
+ self._border = kwargs["border"]
+ else:
+ self._border = True
+ self._hrules = kwargs["hrules"] or FRAME
+ self._vrules = kwargs["vrules"] or ALL
+
+ self._sortby = kwargs["sortby"] or None
+ if kwargs["reversesort"] in (True, False):
+ self._reversesort = kwargs["reversesort"]
+ else:
+ self._reversesort = False
+ self._sort_key = kwargs["sort_key"] or (lambda x: x)
+
+ self._int_format = kwargs["int_format"] or {}
+ self._float_format = kwargs["float_format"] or {}
+ self._padding_width = kwargs["padding_width"] or 1
+ self._left_padding_width = kwargs["left_padding_width"] or None
+ self._right_padding_width = kwargs["right_padding_width"] or None
+
+ self._vertical_char = kwargs["vertical_char"] or self._unicode("|")
+ self._horizontal_char = kwargs["horizontal_char"] or self._unicode("-")
+ self._junction_char = kwargs["junction_char"] or self._unicode("+")
+
+ if kwargs["print_empty"] in (True, False):
+ self._print_empty = kwargs["print_empty"]
+ else:
+ self._print_empty = True
+ self._format = kwargs["format"] or False
+ self._xhtml = kwargs["xhtml"] or False
+ self._attributes = kwargs["attributes"] or {}
+
+ def _unicode(self, value):
+ if not isinstance(value, basestring):
+ value = str(value)
+ if not isinstance(value, unicode):
+ value = unicode(value, self.encoding, "strict")
+ return value
+
+ def _justify(self, text, width, align):
+ excess = width - _str_block_width(text)
+ if align == "l":
+ return text + excess * " "
+ elif align == "r":
+ return excess * " " + text
+ else:
+ if excess % 2:
+ # Uneven padding
+ # Put more space on right if text is of odd length...
+ if _str_block_width(text) % 2:
+ return (excess//2)*" " + text + (excess//2 + 1)*" "
+ # and more space on left if text is of even length
+ else:
+ return (excess//2 + 1)*" " + text + (excess//2)*" "
+ # Why distribute extra space this way? To match the behaviour of
+ # the inbuilt str.center() method.
+ else:
+ # Equal padding on either side
+ return (excess//2)*" " + text + (excess//2)*" "
+
+ def __getattr__(self, name):
+
+ if name == "rowcount":
+ return len(self._rows)
+ elif name == "colcount":
+ if self._field_names:
+ return len(self._field_names)
+ elif self._rows:
+ return len(self._rows[0])
+ else:
+ return 0
+ else:
+ raise AttributeError(name)
+
+ def __getitem__(self, index):
+
+ new = PrettyTable()
+ new.field_names = self.field_names
+ for attr in self._options:
+ setattr(new, "_"+attr, getattr(self, "_"+attr))
+ setattr(new, "_align", getattr(self, "_align"))
+ if isinstance(index, slice):
+ for row in self._rows[index]:
+ new.add_row(row)
+ elif isinstance(index, int):
+ new.add_row(self._rows[index])
+ else:
+ raise Exception("Index %s is invalid, must be an integer or slice" % str(index))
+ return new
+
+ if py3k:
+ def __str__(self):
+ return self.__unicode__()
+ else:
+ def __str__(self):
+ return self.__unicode__().encode(self.encoding)
+
+ def __unicode__(self):
+ return self.get_string()
+
+ ##############################
+ # ATTRIBUTE VALIDATORS #
+ ##############################
+
+ # The method _validate_option is all that should be used elsewhere in the code base to validate options.
+ # It will call the appropriate validation method for that option. The individual validation methods should
+ # never need to be called directly (although nothing bad will happen if they *are*).
+ # Validation happens in TWO places.
+ # Firstly, in the property setters defined in the ATTRIBUTE MANAGMENT section.
+ # Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings
+
+ def _validate_option(self, option, val):
+ if option in ("field_names"):
+ self._validate_field_names(val)
+ elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"):
+ self._validate_nonnegative_int(option, val)
+ elif option in ("sortby"):
+ self._validate_field_name(option, val)
+ elif option in ("sort_key"):
+ self._validate_function(option, val)
+ elif option in ("hrules"):
+ self._validate_hrules(option, val)
+ elif option in ("vrules"):
+ self._validate_vrules(option, val)
+ elif option in ("fields"):
+ self._validate_all_field_names(option, val)
+ elif option in ("header", "border", "reversesort", "xhtml", "print_empty"):
+ self._validate_true_or_false(option, val)
+ elif option in ("header_style"):
+ self._validate_header_style(val)
+ elif option in ("int_format"):
+ self._validate_int_format(option, val)
+ elif option in ("float_format"):
+ self._validate_float_format(option, val)
+ elif option in ("vertical_char", "horizontal_char", "junction_char"):
+ self._validate_single_char(option, val)
+ elif option in ("attributes"):
+ self._validate_attributes(option, val)
+ else:
+ raise Exception("Unrecognised option: %s!" % option)
+
+ def _validate_field_names(self, val):
+ # Check for appropriate length
+ if self._field_names:
+ try:
+ assert len(val) == len(self._field_names)
+ except AssertionError:
+ raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names)))
+ if self._rows:
+ try:
+ assert len(val) == len(self._rows[0])
+ except AssertionError:
+ raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0])))
+ # Check for uniqueness
+ try:
+ assert len(val) == len(set(val))
+ except AssertionError:
+ raise Exception("Field names must be unique!")
+
+ def _validate_header_style(self, val):
+ try:
+ assert val in ("cap", "title", "upper", "lower", None)
+ except AssertionError:
+ raise Exception("Invalid header style, use cap, title, upper, lower or None!")
+
+ def _validate_align(self, val):
+ try:
+ assert val in ["l","c","r"]
+ except AssertionError:
+ raise Exception("Alignment %s is invalid, use l, c or r!" % val)
+
+ def _validate_valign(self, val):
+ try:
+ assert val in ["t","m","b",None]
+ except AssertionError:
+ raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
+
+ def _validate_nonnegative_int(self, name, val):
+ try:
+ assert int(val) >= 0
+ except AssertionError:
+ raise Exception("Invalid value for %s: %s!" % (name, self._unicode(val)))
+
+ def _validate_true_or_false(self, name, val):
+ try:
+ assert val in (True, False)
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be True or False." % name)
+
+ def _validate_int_format(self, name, val):
+ if val == "":
+ return
+ try:
+ assert type(val) in (str, unicode)
+ assert val.isdigit()
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be an integer format string." % name)
+
+ def _validate_float_format(self, name, val):
+ if val == "":
+ return
+ try:
+ assert type(val) in (str, unicode)
+ assert "." in val
+ bits = val.split(".")
+ assert len(bits) <= 2
+ assert bits[0] == "" or bits[0].isdigit()
+ assert bits[1] == "" or bits[1].isdigit()
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be a float format string." % name)
+
+ def _validate_function(self, name, val):
+ try:
+ assert hasattr(val, "__call__")
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be a function." % name)
+
+ def _validate_hrules(self, name, val):
+ try:
+ assert val in (ALL, FRAME, HEADER, NONE)
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be ALL, FRAME, HEADER or NONE." % name)
+
+ def _validate_vrules(self, name, val):
+ try:
+ assert val in (ALL, FRAME, NONE)
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be ALL, FRAME, or NONE." % name)
+
+ def _validate_field_name(self, name, val):
+ try:
+ assert (val in self._field_names) or (val is None)
+ except AssertionError:
+ raise Exception("Invalid field name: %s!" % val)
+
+ def _validate_all_field_names(self, name, val):
+ try:
+ for x in val:
+ self._validate_field_name(name, x)
+ except AssertionError:
+ raise Exception("fields must be a sequence of field names!")
+
+ def _validate_single_char(self, name, val):
+ try:
+ assert _str_block_width(val) == 1
+ except AssertionError:
+ raise Exception("Invalid value for %s! Must be a string of length 1." % name)
+
+ def _validate_attributes(self, name, val):
+ try:
+ assert isinstance(val, dict)
+ except AssertionError:
+ raise Exception("attributes must be a dictionary of name/value pairs!")
+
+ ##############################
+ # ATTRIBUTE MANAGEMENT #
+ ##############################
+
+ def _get_field_names(self):
+ return self._field_names
+ """The names of the fields
+
+ Arguments:
+
+ fields - list or tuple of field names"""
+ def _set_field_names(self, val):
+ val = [self._unicode(x) for x in val]
+ self._validate_option("field_names", val)
+ if self._field_names:
+ old_names = self._field_names[:]
+ self._field_names = val
+ if self._align and old_names:
+ for old_name, new_name in zip(old_names, val):
+ self._align[new_name] = self._align[old_name]
+ for old_name in old_names:
+ if old_name not in self._align:
+ self._align.pop(old_name)
+ else:
+ for field in self._field_names:
+ self._align[field] = "c"
+ if self._valign and old_names:
+ for old_name, new_name in zip(old_names, val):
+ self._valign[new_name] = self._valign[old_name]
+ for old_name in old_names:
+ if old_name not in self._valign:
+ self._valign.pop(old_name)
+ else:
+ for field in self._field_names:
+ self._valign[field] = "t"
+ field_names = property(_get_field_names, _set_field_names)
+
+ def _get_align(self):
+ return self._align
+ def _set_align(self, val):
+ self._validate_align(val)
+ for field in self._field_names:
+ self._align[field] = val
+ align = property(_get_align, _set_align)
+
+ def _get_valign(self):
+ return self._valign
+ def _set_valign(self, val):
+ self._validate_valign(val)
+ for field in self._field_names:
+ self._valign[field] = val
+ valign = property(_get_valign, _set_valign)
+
+ def _get_max_width(self):
+ return self._max_width
+ def _set_max_width(self, val):
+ self._validate_option("max_width", val)
+ for field in self._field_names:
+ self._max_width[field] = val
+ max_width = property(_get_max_width, _set_max_width)
+
+ def _get_fields(self):
+ """List or tuple of field names to include in displays
+
+ Arguments:
+
+ fields - list or tuple of field names to include in displays"""
+ return self._fields
+ def _set_fields(self, val):
+ self._validate_option("fields", val)
+ self._fields = val
+ fields = property(_get_fields, _set_fields)
+
+ def _get_start(self):
+ """Start index of the range of rows to print
+
+ Arguments:
+
+ start - index of first data row to include in output"""
+ return self._start
+
+ def _set_start(self, val):
+ self._validate_option("start", val)
+ self._start = val
+ start = property(_get_start, _set_start)
+
+ def _get_end(self):
+ """End index of the range of rows to print
+
+ Arguments:
+
+ end - index of last data row to include in output PLUS ONE (list slice style)"""
+ return self._end
+ def _set_end(self, val):
+ self._validate_option("end", val)
+ self._end = val
+ end = property(_get_end, _set_end)
+
+ def _get_sortby(self):
+ """Name of field by which to sort rows
+
+ Arguments:
+
+ sortby - field name to sort by"""
+ return self._sortby
+ def _set_sortby(self, val):
+ self._validate_option("sortby", val)
+ self._sortby = val
+ sortby = property(_get_sortby, _set_sortby)
+
+ def _get_reversesort(self):
+ """Controls direction of sorting (ascending vs descending)
+
+ Arguments:
+
+ reveresort - set to True to sort by descending order, or False to sort by ascending order"""
+ return self._reversesort
+ def _set_reversesort(self, val):
+ self._validate_option("reversesort", val)
+ self._reversesort = val
+ reversesort = property(_get_reversesort, _set_reversesort)
+
+ def _get_sort_key(self):
+ """Sorting key function, applied to data points before sorting
+
+ Arguments:
+
+ sort_key - a function which takes one argument and returns something to be sorted"""
+ return self._sort_key
+ def _set_sort_key(self, val):
+ self._validate_option("sort_key", val)
+ self._sort_key = val
+ sort_key = property(_get_sort_key, _set_sort_key)
+
+ def _get_header(self):
+ """Controls printing of table header with field names
+
+ Arguments:
+
+ header - print a header showing field names (True or False)"""
+ return self._header
+ def _set_header(self, val):
+ self._validate_option("header", val)
+ self._header = val
+ header = property(_get_header, _set_header)
+
+ def _get_header_style(self):
+ """Controls stylisation applied to field names in header
+
+ Arguments:
+
+ header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)"""
+ return self._header_style
+ def _set_header_style(self, val):
+ self._validate_header_style(val)
+ self._header_style = val
+ header_style = property(_get_header_style, _set_header_style)
+
+ def _get_border(self):
+ """Controls printing of border around table
+
+ Arguments:
+
+ border - print a border around the table (True or False)"""
+ return self._border
+ def _set_border(self, val):
+ self._validate_option("border", val)
+ self._border = val
+ border = property(_get_border, _set_border)
+
+ def _get_hrules(self):
+ """Controls printing of horizontal rules after rows
+
+ Arguments:
+
+ hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
+ return self._hrules
+ def _set_hrules(self, val):
+ self._validate_option("hrules", val)
+ self._hrules = val
+ hrules = property(_get_hrules, _set_hrules)
+
+ def _get_vrules(self):
+ """Controls printing of vertical rules between columns
+
+ Arguments:
+
+ vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
+ return self._vrules
+ def _set_vrules(self, val):
+ self._validate_option("vrules", val)
+ self._vrules = val
+ vrules = property(_get_vrules, _set_vrules)
+
+ def _get_int_format(self):
+ """Controls formatting of integer data
+ Arguments:
+
+ int_format - integer format string"""
+ return self._int_format
+ def _set_int_format(self, val):
+# self._validate_option("int_format", val)
+ for field in self._field_names:
+ self._int_format[field] = val
+ int_format = property(_get_int_format, _set_int_format)
+
+ def _get_float_format(self):
+ """Controls formatting of floating point data
+ Arguments:
+
+ float_format - floating point format string"""
+ return self._float_format
+ def _set_float_format(self, val):
+# self._validate_option("float_format", val)
+ for field in self._field_names:
+ self._float_format[field] = val
+ float_format = property(_get_float_format, _set_float_format)
+
+ def _get_padding_width(self):
+ """The number of empty spaces between a column's edge and its content
+
+ Arguments:
+
+ padding_width - number of spaces, must be a positive integer"""
+ return self._padding_width
+ def _set_padding_width(self, val):
+ self._validate_option("padding_width", val)
+ self._padding_width = val
+ padding_width = property(_get_padding_width, _set_padding_width)
+
+ def _get_left_padding_width(self):
+ """The number of empty spaces between a column's left edge and its content
+
+ Arguments:
+
+ left_padding - number of spaces, must be a positive integer"""
+ return self._left_padding_width
+ def _set_left_padding_width(self, val):
+ self._validate_option("left_padding_width", val)
+ self._left_padding_width = val
+ left_padding_width = property(_get_left_padding_width, _set_left_padding_width)
+
+ def _get_right_padding_width(self):
+ """The number of empty spaces between a column's right edge and its content
+
+ Arguments:
+
+ right_padding - number of spaces, must be a positive integer"""
+ return self._right_padding_width
+ def _set_right_padding_width(self, val):
+ self._validate_option("right_padding_width", val)
+ self._right_padding_width = val
+ right_padding_width = property(_get_right_padding_width, _set_right_padding_width)
+
+ def _get_vertical_char(self):
+ """The charcter used when printing table borders to draw vertical lines
+
+ Arguments:
+
+ vertical_char - single character string used to draw vertical lines"""
+ return self._vertical_char
+ def _set_vertical_char(self, val):
+ val = self._unicode(val)
+ self._validate_option("vertical_char", val)
+ self._vertical_char = val
+ vertical_char = property(_get_vertical_char, _set_vertical_char)
+
+ def _get_horizontal_char(self):
+ """The charcter used when printing table borders to draw horizontal lines
+
+ Arguments:
+
+ horizontal_char - single character string used to draw horizontal lines"""
+ return self._horizontal_char
+ def _set_horizontal_char(self, val):
+ val = self._unicode(val)
+ self._validate_option("horizontal_char", val)
+ self._horizontal_char = val
+ horizontal_char = property(_get_horizontal_char, _set_horizontal_char)
+
+ def _get_junction_char(self):
+ """The charcter used when printing table borders to draw line junctions
+
+ Arguments:
+
+ junction_char - single character string used to draw line junctions"""
+ return self._junction_char
+ def _set_junction_char(self, val):
+ val = self._unicode(val)
+ self._validate_option("vertical_char", val)
+ self._junction_char = val
+ junction_char = property(_get_junction_char, _set_junction_char)
+
+ def _get_format(self):
+ """Controls whether or not HTML tables are formatted to match styling options
+
+ Arguments:
+
+ format - True or False"""
+ return self._format
+ def _set_format(self, val):
+ self._validate_option("format", val)
+ self._format = val
+ format = property(_get_format, _set_format)
+
+ def _get_print_empty(self):
+ """Controls whether or not empty tables produce a header and frame or just an empty string
+
+ Arguments:
+
+ print_empty - True or False"""
+ return self._print_empty
+ def _set_print_empty(self, val):
+ self._validate_option("print_empty", val)
+ self._print_empty = val
+ print_empty = property(_get_print_empty, _set_print_empty)
+
+ def _get_attributes(self):
+ """A dictionary of HTML attribute name/value pairs to be included in the tag when printing HTML
+
+ Arguments:
+
+ attributes - dictionary of attributes"""
+ return self._attributes
+ def _set_attributes(self, val):
+ self._validate_option("attributes", val)
+ self._attributes = val
+ attributes = property(_get_attributes, _set_attributes)
+
+ ##############################
+ # OPTION MIXER #
+ ##############################
+
+ def _get_options(self, kwargs):
+
+ options = {}
+ for option in self._options:
+ if option in kwargs:
+ self._validate_option(option, kwargs[option])
+ options[option] = kwargs[option]
+ else:
+ options[option] = getattr(self, "_"+option)
+ return options
+
+ ##############################
+ # PRESET STYLE LOGIC #
+ ##############################
+
+ def set_style(self, style):
+
+ if style == DEFAULT:
+ self._set_default_style()
+ elif style == MSWORD_FRIENDLY:
+ self._set_msword_style()
+ elif style == PLAIN_COLUMNS:
+ self._set_columns_style()
+ elif style == RANDOM:
+ self._set_random_style()
+ else:
+ raise Exception("Invalid pre-set style!")
+
+ def _set_default_style(self):
+
+ self.header = True
+ self.border = True
+ self._hrules = FRAME
+ self._vrules = ALL
+ self.padding_width = 1
+ self.left_padding_width = 1
+ self.right_padding_width = 1
+ self.vertical_char = "|"
+ self.horizontal_char = "-"
+ self.junction_char = "+"
+
+ def _set_msword_style(self):
+
+ self.header = True
+ self.border = True
+ self._hrules = NONE
+ self.padding_width = 1
+ self.left_padding_width = 1
+ self.right_padding_width = 1
+ self.vertical_char = "|"
+
+ def _set_columns_style(self):
+
+ self.header = True
+ self.border = False
+ self.padding_width = 1
+ self.left_padding_width = 0
+ self.right_padding_width = 8
+
+ def _set_random_style(self):
+
+ # Just for fun!
+ self.header = random.choice((True, False))
+ self.border = random.choice((True, False))
+ self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
+ self._vrules = random.choice((ALL, FRAME, NONE))
+ self.left_padding_width = random.randint(0,5)
+ self.right_padding_width = random.randint(0,5)
+ self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
+ self.horizontal_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
+ self.junction_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
+
+ ##############################
+ # DATA INPUT METHODS #
+ ##############################
+
+ def add_row(self, row):
+
+ """Add a row to the table
+
+ Arguments:
+
+ row - row of data, should be a list with as many elements as the table
+ has fields"""
+
+ if self._field_names and len(row) != len(self._field_names):
+ raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names)))
+ if not self._field_names:
+ self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))]
+ self._rows.append(list(row))
+
+ def del_row(self, row_index):
+
+ """Delete a row to the table
+
+ Arguments:
+
+ row_index - The index of the row you want to delete. Indexing starts at 0."""
+
+ if row_index > len(self._rows)-1:
+ raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
+ del self._rows[row_index]
+
+ def add_column(self, fieldname, column, align="c", valign="t"):
+
+ """Add a column to the table.
+
+ Arguments:
+
+ fieldname - name of the field to contain the new column of data
+ column - column of data, should be a list with as many elements as the
+ table has rows
+ align - desired alignment for this column - "l" for left, "c" for centre and "r" for right
+ valign - desired vertical alignment for new columns - "t" for top, "m" for middle and "b" for bottom"""
+
+ if len(self._rows) in (0, len(column)):
+ self._validate_align(align)
+ self._validate_valign(valign)
+ self._field_names.append(fieldname)
+ self._align[fieldname] = align
+ self._valign[fieldname] = valign
+ for i in range(0, len(column)):
+ if len(self._rows) < i+1:
+ self._rows.append([])
+ self._rows[i].append(column[i])
+ else:
+ raise Exception("Column length %d does not match number of rows %d!" % (len(column), len(self._rows)))
+
+ def clear_rows(self):
+
+ """Delete all rows from the table but keep the current field names"""
+
+ self._rows = []
+
+ def clear(self):
+
+ """Delete all rows and field names from the table, maintaining nothing but styling options"""
+
+ self._rows = []
+ self._field_names = []
+ self._widths = []
+
+ ##############################
+ # MISC PUBLIC METHODS #
+ ##############################
+
+ def copy(self):
+ return copy.deepcopy(self)
+
+ ##############################
+ # MISC PRIVATE METHODS #
+ ##############################
+
+ def _format_value(self, field, value):
+ if isinstance(value, int) and field in self._int_format:
+ value = self._unicode(("%%%sd" % self._int_format[field]) % value)
+ elif isinstance(value, float) and field in self._float_format:
+ value = self._unicode(("%%%sf" % self._float_format[field]) % value)
+ return self._unicode(value)
+
+ def _compute_widths(self, rows, options):
+ if options["header"]:
+ widths = [_get_size(field)[0] for field in self._field_names]
+ else:
+ widths = len(self.field_names) * [0]
+ for row in rows:
+ for index, value in enumerate(row):
+ fieldname = self.field_names[index]
+ if fieldname in self.max_width:
+ widths[index] = max(widths[index], min(_get_size(value)[0], self.max_width[fieldname]))
+ else:
+ widths[index] = max(widths[index], _get_size(value)[0])
+ self._widths = widths
+
+ def _get_padding_widths(self, options):
+
+ if options["left_padding_width"] is not None:
+ lpad = options["left_padding_width"]
+ else:
+ lpad = options["padding_width"]
+ if options["right_padding_width"] is not None:
+ rpad = options["right_padding_width"]
+ else:
+ rpad = options["padding_width"]
+ return lpad, rpad
+
+ def _get_rows(self, options):
+ """Return only those data rows that should be printed, based on slicing and sorting.
+
+ Arguments:
+
+ options - dictionary of option settings."""
+
+ # Make a copy of only those rows in the slice range
+ rows = copy.deepcopy(self._rows[options["start"]:options["end"]])
+ # Sort if necessary
+ if options["sortby"]:
+ sortindex = self._field_names.index(options["sortby"])
+ # Decorate
+ rows = [[row[sortindex]]+row for row in rows]
+ # Sort
+ rows.sort(reverse=options["reversesort"], key=options["sort_key"])
+ # Undecorate
+ rows = [row[1:] for row in rows]
+ return rows
+
+ def _format_row(self, row, options):
+ return [self._format_value(field, value) for (field, value) in zip(self._field_names, row)]
+
+ def _format_rows(self, rows, options):
+ return [self._format_row(row, options) for row in rows]
+
+ ##############################
+ # PLAIN TEXT STRING METHODS #
+ ##############################
+
+ def get_string(self, **kwargs):
+
+ """Return string representation of table in current state.
+
+ Arguments:
+
+ start - index of first data row to include in output
+ end - index of last data row to include in output PLUS ONE (list slice style)
+ fields - names of fields (columns) to include
+ header - print a header showing field names (True or False)
+ border - print a border around the table (True or False)
+ hrules - controls printing of horizontal rules after rows. Allowed values: ALL, FRAME, HEADER, NONE
+ vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
+ int_format - controls formatting of integer data
+ float_format - controls formatting of floating point data
+ padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
+ left_padding_width - number of spaces on left hand side of column data
+ right_padding_width - number of spaces on right hand side of column data
+ vertical_char - single character string used to draw vertical lines
+ horizontal_char - single character string used to draw horizontal lines
+ junction_char - single character string used to draw line junctions
+ sortby - name of field to sort rows by
+ sort_key - sorting key function, applied to data points before sorting
+ reversesort - True or False to sort in descending or ascending order
+ print empty - if True, stringify just the header for an empty table, if False return an empty string """
+
+ options = self._get_options(kwargs)
+
+ lines = []
+
+ # Don't think too hard about an empty table
+ # Is this the desired behaviour? Maybe we should still print the header?
+ if self.rowcount == 0 and (not options["print_empty"] or not options["border"]):
+ return ""
+
+ # Get the rows we need to print, taking into account slicing, sorting, etc.
+ rows = self._get_rows(options)
+
+ # Turn all data in all rows into Unicode, formatted as desired
+ formatted_rows = self._format_rows(rows, options)
+
+ # Compute column widths
+ self._compute_widths(formatted_rows, options)
+
+ # Add header or top of border
+ self._hrule = self._stringify_hrule(options)
+ if options["header"]:
+ lines.append(self._stringify_header(options))
+ elif options["border"] and options["hrules"] in (ALL, FRAME):
+ lines.append(self._hrule)
+
+ # Add rows
+ for row in formatted_rows:
+ lines.append(self._stringify_row(row, options))
+
+ # Add bottom of border
+ if options["border"] and options["hrules"] == FRAME:
+ lines.append(self._hrule)
+
+ return self._unicode("\n").join(lines)
+
+ def _stringify_hrule(self, options):
+
+ if not options["border"]:
+ return ""
+ lpad, rpad = self._get_padding_widths(options)
+ if options['vrules'] in (ALL, FRAME):
+ bits = [options["junction_char"]]
+ else:
+ bits = [options["horizontal_char"]]
+ # For tables with no data or fieldnames
+ if not self._field_names:
+ bits.append(options["junction_char"])
+ return "".join(bits)
+ for field, width in zip(self._field_names, self._widths):
+ if options["fields"] and field not in options["fields"]:
+ continue
+ bits.append((width+lpad+rpad)*options["horizontal_char"])
+ if options['vrules'] == ALL:
+ bits.append(options["junction_char"])
+ else:
+ bits.append(options["horizontal_char"])
+ if options["vrules"] == FRAME:
+ bits.pop()
+ bits.append(options["junction_char"])
+ return "".join(bits)
+
+ def _stringify_header(self, options):
+
+ bits = []
+ lpad, rpad = self._get_padding_widths(options)
+ if options["border"]:
+ if options["hrules"] in (ALL, FRAME):
+ bits.append(self._hrule)
+ bits.append("\n")
+ if options["vrules"] in (ALL, FRAME):
+ bits.append(options["vertical_char"])
+ else:
+ bits.append(" ")
+ # For tables with no data or field names
+ if not self._field_names:
+ if options["vrules"] in (ALL, FRAME):
+ bits.append(options["vertical_char"])
+ else:
+ bits.append(" ")
+ for field, width, in zip(self._field_names, self._widths):
+ if options["fields"] and field not in options["fields"]:
+ continue
+ if self._header_style == "cap":
+ fieldname = field.capitalize()
+ elif self._header_style == "title":
+ fieldname = field.title()
+ elif self._header_style == "upper":
+ fieldname = field.upper()
+ elif self._header_style == "lower":
+ fieldname = field.lower()
+ else:
+ fieldname = field
+ bits.append(" " * lpad + self._justify(fieldname, width, self._align[field]) + " " * rpad)
+ if options["border"]:
+ if options["vrules"] == ALL:
+ bits.append(options["vertical_char"])
+ else:
+ bits.append(" ")
+ # If vrules is FRAME, then we just appended a space at the end
+ # of the last field, when we really want a vertical character
+ if options["border"] and options["vrules"] == FRAME:
+ bits.pop()
+ bits.append(options["vertical_char"])
+ if options["border"] and options["hrules"] != NONE:
+ bits.append("\n")
+ bits.append(self._hrule)
+ return "".join(bits)
+
+ def _stringify_row(self, row, options):
+
+ for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths):
+ # Enforce max widths
+ lines = value.split("\n")
+ new_lines = []
+ for line in lines:
+ if _str_block_width(line) > width:
+ line = textwrap.fill(line, width)
+ new_lines.append(line)
+ lines = new_lines
+ value = "\n".join(lines)
+ row[index] = value
+
+ row_height = 0
+ for c in row:
+ h = _get_size(c)[1]
+ if h > row_height:
+ row_height = h
+
+ bits = []
+ lpad, rpad = self._get_padding_widths(options)
+ for y in range(0, row_height):
+ bits.append([])
+ if options["border"]:
+ if options["vrules"] in (ALL, FRAME):
+ bits[y].append(self.vertical_char)
+ else:
+ bits[y].append(" ")
+
+ for field, value, width, in zip(self._field_names, row, self._widths):
+
+ valign = self._valign[field]
+ lines = value.split("\n")
+ dHeight = row_height - len(lines)
+ if dHeight:
+ if valign == "m":
+ lines = [""] * int(dHeight / 2) + lines + [""] * (dHeight - int(dHeight / 2))
+ elif valign == "b":
+ lines = [""] * dHeight + lines
+ else:
+ lines = lines + [""] * dHeight
+
+ y = 0
+ for l in lines:
+ if options["fields"] and field not in options["fields"]:
+ continue
+
+ bits[y].append(" " * lpad + self._justify(l, width, self._align[field]) + " " * rpad)
+ if options["border"]:
+ if options["vrules"] == ALL:
+ bits[y].append(self.vertical_char)
+ else:
+ bits[y].append(" ")
+ y += 1
+
+ # If vrules is FRAME, then we just appended a space at the end
+ # of the last field, when we really want a vertical character
+ for y in range(0, row_height):
+ if options["border"] and options["vrules"] == FRAME:
+ bits[y].pop()
+ bits[y].append(options["vertical_char"])
+
+ if options["border"] and options["hrules"]== ALL:
+ bits[row_height-1].append("\n")
+ bits[row_height-1].append(self._hrule)
+
+ for y in range(0, row_height):
+ bits[y] = "".join(bits[y])
+
+ return "\n".join(bits)
+
+ ##############################
+ # HTML STRING METHODS #
+ ##############################
+
+ def get_html_string(self, **kwargs):
+
+ """Return string representation of HTML formatted version of table in current state.
+
+ Arguments:
+
+ start - index of first data row to include in output
+ end - index of last data row to include in output PLUS ONE (list slice style)
+ fields - names of fields (columns) to include
+ header - print a header showing field names (True or False)
+ border - print a border around the table (True or False)
+ hrules - controls printing of horizontal rules after rows. Allowed values: ALL, FRAME, HEADER, NONE
+ vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
+ int_format - controls formatting of integer data
+ float_format - controls formatting of floating point data
+ padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
+ left_padding_width - number of spaces on left hand side of column data
+ right_padding_width - number of spaces on right hand side of column data
+ sortby - name of field to sort rows by
+ sort_key - sorting key function, applied to data points before sorting
+ attributes - dictionary of name/value pairs to include as HTML attributes in the tag
+ xhtml - print
tags if True,
tags if false"""
+
+ options = self._get_options(kwargs)
+
+ if options["format"]:
+ string = self._get_formatted_html_string(options)
+ else:
+ string = self._get_simple_html_string(options)
+
+ return string
+
+ def _get_simple_html_string(self, options):
+
+ lines = []
+ if options["xhtml"]:
+ linebreak = "
"
+ else:
+ linebreak = "
"
+
+ open_tag = []
+ open_tag.append("")
+ lines.append("".join(open_tag))
+
+ # Headers
+ if options["header"]:
+ lines.append(" ")
+ for field in self._field_names:
+ if options["fields"] and field not in options["fields"]:
+ continue
+ lines.append(" | %s | " % escape(field).replace("\n", linebreak))
+ lines.append("
")
+
+ # Data
+ rows = self._get_rows(options)
+ formatted_rows = self._format_rows(rows, options)
+ for row in formatted_rows:
+ lines.append(" ")
+ for field, datum in zip(self._field_names, row):
+ if options["fields"] and field not in options["fields"]:
+ continue
+ lines.append(" | %s | " % escape(datum).replace("\n", linebreak))
+ lines.append("
")
+
+ lines.append("
")
+
+ return self._unicode("\n").join(lines)
+
+ def _get_formatted_html_string(self, options):
+
+ lines = []
+ lpad, rpad = self._get_padding_widths(options)
+ if options["xhtml"]:
+ linebreak = "
"
+ else:
+ linebreak = "
"
+
+ open_tag = []
+ open_tag.append("")
+ lines.append("".join(open_tag))
+
+ # Headers
+ if options["header"]:
+ lines.append(" ")
+ for field in self._field_names:
+ if options["fields"] and field not in options["fields"]:
+ continue
+ lines.append(" | %s | " % (lpad, rpad, escape(field).replace("\n", linebreak)))
+ lines.append("
")
+
+ # Data
+ rows = self._get_rows(options)
+ formatted_rows = self._format_rows(rows, options)
+ aligns = []
+ valigns = []
+ for field in self._field_names:
+ aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]])
+ valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]])
+ for row in formatted_rows:
+ lines.append(" ")
+ for field, datum, align, valign in zip(self._field_names, row, aligns, valigns):
+ if options["fields"] and field not in options["fields"]:
+ continue
+ lines.append(" | %s | " % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
+ lines.append("
")
+ lines.append("
")
+
+ return self._unicode("\n").join(lines)
+
+##############################
+# UNICODE WIDTH FUNCTIONS #
+##############################
+
+def _char_block_width(char):
+ # Basic Latin, which is probably the most common case
+ #if char in xrange(0x0021, 0x007e):
+ #if char >= 0x0021 and char <= 0x007e:
+ if 0x0021 <= char <= 0x007e:
+ return 1
+ # Chinese, Japanese, Korean (common)
+ if 0x4e00 <= char <= 0x9fff:
+ return 2
+ # Hangul
+ if 0xac00 <= char <= 0xd7af:
+ return 2
+ # Combining?
+ if unicodedata.combining(uni_chr(char)):
+ return 0
+ # Hiragana and Katakana
+ if 0x3040 <= char <= 0x309f or 0x30a0 <= char <= 0x30ff:
+ return 2
+ # Full-width Latin characters
+ if 0xff01 <= char <= 0xff60:
+ return 2
+ # CJK punctuation
+ if 0x3000 <= char <= 0x303e:
+ return 2
+ # Backspace and delete
+ if char in (0x0008, 0x007f):
+ return -1
+ # Other control characters
+ elif char in (0x0000, 0x001f):
+ return 0
+ # Take a guess
+ return 1
+
+def _str_block_width(val):
+
+ return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val))))
+
+##############################
+# TABLE FACTORIES #
+##############################
+
+def from_csv(fp, field_names = None, **kwargs):
+
+ dialect = csv.Sniffer().sniff(fp.read(1024))
+ fp.seek(0)
+ reader = csv.reader(fp, dialect)
+
+ table = PrettyTable(**kwargs)
+ if field_names:
+ table.field_names = field_names
+ else:
+ if py3k:
+ table.field_names = [x.strip() for x in next(reader)]
+ else:
+ table.field_names = [x.strip() for x in reader.next()]
+
+ for row in reader:
+ table.add_row([x.strip() for x in row])
+
+ return table
+
+def from_db_cursor(cursor, **kwargs):
+
+ if cursor.description:
+ table = PrettyTable(**kwargs)
+ table.field_names = [col[0] for col in cursor.description]
+ for row in cursor.fetchall():
+ table.add_row(row)
+ return table
+
+class TableHandler(HTMLParser):
+
+ def __init__(self, **kwargs):
+ HTMLParser.__init__(self)
+ self.kwargs = kwargs
+ self.tables = []
+ self.last_row = []
+ self.rows = []
+ self.max_row_width = 0
+ self.active = None
+ self.last_content = ""
+ self.is_last_row_header = False
+
+ def handle_starttag(self,tag, attrs):
+ self.active = tag
+ if tag == "th":
+ self.is_last_row_header = True
+
+ def handle_endtag(self,tag):
+ if tag in ["th", "td"]:
+ stripped_content = self.last_content.strip()
+ self.last_row.append(stripped_content)
+ if tag == "tr":
+ self.rows.append(
+ (self.last_row, self.is_last_row_header))
+ self.max_row_width = max(self.max_row_width, len(self.last_row))
+ self.last_row = []
+ self.is_last_row_header = False
+ if tag == "table":
+ table = self.generate_table(self.rows)
+ self.tables.append(table)
+ self.rows = []
+ self.last_content = " "
+ self.active = None
+
+
+ def handle_data(self, data):
+ self.last_content += data
+
+ def generate_table(self, rows):
+ """
+ Generates from a list of rows a PrettyTable object.
+ """
+ table = PrettyTable(**self.kwargs)
+ for row in self.rows:
+ if len(row[0]) < self.max_row_width:
+ appends = self.max_row_width - len(row[0])
+ for i in range(1,appends):
+ row[0].append("-")
+
+ if row[1] == True:
+ self.make_fields_unique(row[0])
+ table.field_names = row[0]
+ else:
+ table.add_row(row[0])
+ return table
+
+ def make_fields_unique(self, fields):
+ """
+ iterates over the row and make each field unique
+ """
+ for i in range(0, len(fields)):
+ for j in range(i+1, len(fields)):
+ if fields[i] == fields[j]:
+ fields[j] += "'"
+
+def from_html(html_code, **kwargs):
+ """
+ Generates a list of PrettyTables from a string of HTML code. Each in
+ the HTML becomes one PrettyTable object.
+ """
+
+ parser = TableHandler(**kwargs)
+ parser.feed(html_code)
+ return parser.tables
+
+def from_html_one(html_code, **kwargs):
+ """
+ Generates a PrettyTables from a string of HTML code which contains only a
+ single
+ """
+
+ tables = from_html(html_code, **kwargs)
+ try:
+ assert len(tables) == 1
+ except AssertionError:
+ raise Exception("More than one in provided HTML code! Use from_html instead.")
+ return tables[0]
+
+##############################
+# MAIN (TEST FUNCTION) #
+##############################
+
+def main():
+
+ x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
+ x.sortby = "Population"
+ x.reversesort = True
+ x.int_format["Area"] = "04d"
+ x.float_format = "6.1f"
+ x.align["City name"] = "l" # Left align city names
+ x.add_row(["Adelaide", 1295, 1158259, 600.5])
+ x.add_row(["Brisbane", 5905, 1857594, 1146.4])
+ x.add_row(["Darwin", 112, 120900, 1714.7])
+ x.add_row(["Hobart", 1357, 205556, 619.5])
+ x.add_row(["Sydney", 2058, 4336374, 1214.8])
+ x.add_row(["Melbourne", 1566, 3806092, 646.9])
+ x.add_row(["Perth", 5386, 1554769, 869.4])
+ print(x)
+
+if __name__ == "__main__":
+ main()