        1 #!/bin/env python
        2 #
        3 # File: RDKitDrawMoleculesAndDataTable.py
        4 # Author: Manish Sud <msud@san.rr.com>
        5 #
        6 # Copyright (C) 2018 Manish Sud. All rights reserved.
        7 #
        8 # The functionality available in this script is implemented using RDKit, an
        9 # open source toolkit for cheminformatics developed by Greg Landrum.
       10 #
       11 # This file is part of MayaChemTools.
       12 #
       13 # MayaChemTools is free software; you can redistribute it and/or modify it under
       14 # the terms of the GNU Lesser General Public License as published by the Free
       15 # Software Foundation; either version 3 of the License, or (at your option) any
       16 # later version.
       17 #
       18 # MayaChemTools is distributed in the hope that it will be useful, but without
       19 # any warranty; without even the implied warranty of merchantability of fitness
       20 # for a particular purpose.  See the GNU Lesser General Public License for more
       21 # details.
       22 #
       23 # You should have received a copy of the GNU Lesser General Public License
       24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
       25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
       26 # Boston, MA, 02111-1307, USA.
       27 #
       28
       29 from __future__ import print_function
       30
       31 # Add local python path to the global path and import standard library modules...
       32 import os
       33 import sys;  sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
       34 import time
       35 import re
       36 import random
       37
       38 # RDKit imports...
       39 try:
       40     from rdkit import rdBase
       41     from rdkit import Chem
       42     from rdkit.Chem import AllChem
       43 except ImportError as ErrMsg:
       44     sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
       45     sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
       46     sys.exit(1)
       47
       48 # MayaChemTools imports...
       49 try:
       50     from docopt import docopt
       51     import MiscUtil
       52     import RDKitUtil
       53 except ImportError as ErrMsg:
       54     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
       55     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
       56     sys.exit(1)
       57
       58 ScriptName = os.path.basename(sys.argv[0])
       59 Options = {}
       60 OptionsInfo = {}
       61
       62 def main():
       63     """Start execution of the script"""
       64
       65     MiscUtil.PrintInfo("\n%s (RDK v%s; %s): Starting...\n" % (ScriptName, rdBase.rdkitVersion, time.asctime()))
       66
       67     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
       68
       69     # Retrieve command line arguments and options...
       70     RetrieveOptions()
       71
       72     # Process and validate command line arguments and options...
       73     ProcessOptions()
       74
       75     # Perform actions required by the script...
       76     GenerateMoleculesAndDataTable()
       77
       78     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
       79     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
       80
       81 def GenerateMoleculesAndDataTable():
       82     """Generate a HTML table containing molecules and alphanumerical data."""
       83
       84     # Retrieve data...
       85     ValidMols = RetrieveMoleculesAndData()
       86
       87     # Setup data type map...
       88     DataMap = IdentifyStructureAndNumericalData(ValidMols)
       89
       90     # Validate data labels used to specify highlighting data...
       91     ValidateSpecifiedDataLabels(DataMap)
       92
       93     # Validate show molecule name option...
       94     ValidateShowMolNameOption(DataMap)
       95
       96     # Compute 2D coordinates before alignment...
       97     if OptionsInfo["Compute2DCoords"]:
       98         MiscUtil.PrintInfo("\nComputing 2D coordinates for primary structure data...")
       99         for Mol in ValidMols:
      100             AllChem.Compute2DCoords(Mol)
      101
      102     # Perform alignment to a common template for primary molecular structure data...
      103     PerformAlignment(ValidMols)
      104
      105     # Write out a HTML file...
      106     WriteHTMLTableFile(ValidMols, DataMap)
      107
      108 def WriteHTMLTableFile(ValidMols, DataMap):
      109     """Write out a HTML table file."""
      110
      111     Outfile = OptionsInfo["Outfile"]
      112
      113     Writer = open(Outfile, "w")
      114     if Writer is None:
      115         MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
      116
      117     MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile)
      118
      119     WriteHTMLPageHeader(Writer, DataMap)
      120     WriteHTMLPageTitle(Writer)
      121
      122     WriteHTMLTableHeader(Writer)
      123     WriteHTMLTableRows(Writer, ValidMols, DataMap)
      124     WriteHTMLTableEnd(Writer)
      125
      126     WriteHTMLPageFooter(Writer)
      127     WriteHTMLPageEnd(Writer)
      128
      129     if Writer is not None:
      130         Writer.close()
      131
      132 def WriteHTMLTableRows(Writer, ValidMols, DataMap):
      133     """Write out HTML table rows."""
      134
      135     WriteTableHeaderRow(Writer, ValidMols, DataMap)
      136     WriteTableDataRows(Writer, ValidMols, DataMap)
      137     WriteTableFooterRow(Writer, ValidMols, DataMap)
      138
      139 def WriteTableDataRows(Writer, ValidMols, DataMap):
      140     """Write out table data row."""
      141
      142     Writer.write("""        <tbody>\n""")
      143
      144     MolCount = 0
      145     for Mol in ValidMols:
      146         MolCount += 1
      147         Writer.write("""          <tr>\n""")
      148
      149         if OptionsInfo["CounterCol"]:
      150             Writer.write("""            <td></td>\n""")
      151
      152         SetupPrimaryStructureTableData(Writer, Mol)
      153
      154         if OptionsInfo["ShowMolName"]:
      155             MolName = RDKitUtil.GetMolName(Mol, MolCount)
      156             WrappedMolName = MiscUtil.WrapText(MolName, "<br/>", OptionsInfo["WrapTextWidth"])
      157             Writer.write("""            <td>%s</td>\n""" % WrappedMolName)
      158
      159         # Set up rest of the data..
      160         AvailableDataLabelsMap = Mol.GetPropsAsDict(includePrivate = False, includeComputed = False)
      161         for DataLabel in DataMap["DataLabels"]:
      162             if not DataLabel in AvailableDataLabelsMap:
      163                 Writer.write("""            <td></td>\n""")
      164                 continue
      165
      166             # Check for empty value...
      167             DataValue = "%s" % AvailableDataLabelsMap[DataLabel]
      168             DataValue = DataValue.strip()
      169             if MiscUtil.IsEmpty(DataValue):
      170                 Writer.write("""            <td></td>\n""")
      171                 continue
      172
      173             if DataMap["StructureDataMap"][DataLabel]:
      174                 SetupNonPrimaryStructureTableData(Writer, DataLabel, DataValue, DataMap)
      175             else:
      176                 SetupAlphanumericTableData(Writer, DataLabel, DataValue, DataMap)
      177
      178         Writer.write("""          </tr>\n""")
      179
      180     Writer.write("""        </tbody>\n""")
      181
      182 def SetupPrimaryStructureTableData(Writer, Mol):
      183     """Set up an inline SVG image for primary structure data for a table cell."""
      184
      185     HightlightAtomList = SetupAtomListToHighlight(Mol, "Structure")
      186     SVGImageTag = SetupMolInLineSVGImageTag(Mol, HightlightAtomList)
      187
      188     Writer.write("""            <td bgcolor="white"><%s></td>\n""" % SVGImageTag)
      189
      190 def SetupNonPrimaryStructureTableData(Writer, DataLabel, DataValue, DataMap):
      191     """Set up an inline SVG image for non primary structure data cell."""
      192
      193     WrappedDataValue = DataValue
      194     if OptionsInfo["WrapText"]:
      195         WrappedDataValue = MiscUtil.WrapText(DataValue, "<br/>", OptionsInfo["WrapTextWidth"])
      196
      197     if DataMap["SMILESDataMap"][DataLabel]:
      198         Mol = Chem.MolFromSmiles(DataValue, sanitize = False)
      199         Mol.UpdatePropertyCache(strict = False)
      200     else:
      201         MiscUtil.PrintWarning("\nIgnoring uknown structure data column type with column label %s: %s\n" % (DataLabel, DataValue))
      202         Writer.write("""            <td>%s</td>\n""" % WrappedDataValue)
      203         return
      204
      205     if Mol is None:
      206         MiscUtil.PrintWarning("\nSMILES parsing failed for data label %s: %s\n" % (DataLabel, DataValue))
      207         Writer.write("""            <td>%s</td>\n""" % WrappedDataValue)
      208         return
      209     elif  not Mol.GetNumHeavyAtoms():
      210         Writer.write("""            <td>%s</td>\n""" % WrappedDataValue)
      211         return
      212     elif AllChem.Compute2DCoords(Mol) < 0:
      213         Writer.write("""            <td>%s</td>\n""" % WrappedDataValue)
      214         return
      215
      216     HightlightAtomList = SetupAtomListToHighlight(Mol, DataLabel)
      217     SVGImageTag = SetupMolInLineSVGImageTag(Mol, HightlightAtomList)
      218
      219     Writer.write("""            <td bgcolor="white"><%s></td>\n""" % SVGImageTag)
      220
      221 def SetupAlphanumericTableData(Writer, DataLabel, DataValue, DataMap):
      222     """Set up alphanumeric data."""
      223
      224     BackgroundColor, BackgroundColorType = GetAlphanumeircValueHighlightBackgroundColor(DataLabel, DataValue, DataMap)
      225     SetupAlphanumericTableDataValue(Writer, DataValue, BackgroundColor, BackgroundColorType)
      226
      227 def WriteTableHeaderRow(Writer, ValidMols, DataMap):
      228     """Write out table header row."""
      229
      230     TableHeaderStyle = OptionsInfo["TableHeaderStyle"]
      231     if TableHeaderStyle is None:
      232         Writer.write("""      <thead>\n""")
      233         Writer.write("""        <tr>\n""")
      234     elif re.match("^(thead|table)", TableHeaderStyle):
      235         Writer.write("""      <thead class="%s">\n""" % TableHeaderStyle)
      236         Writer.write("""        <tr>\n""")
      237     else:
      238         Writer.write("""      <thead>\n""")
      239         Writer.write("""        <tr bgcolor="%s"\n""" % TableHeaderStyle)
      240
      241     if OptionsInfo["CounterCol"]:
      242         Writer.write("""          <th></th>\n""")
      243     Writer.write("""          <th>Structure</th>\n""")
      244     if OptionsInfo["ShowMolName"]:
      245         Writer.write("""          <th>%s</th>\n""" % OptionsInfo["ShowMolNameDataLabel"])
      246
      247     # Write out rest of the column headers...
      248     for DataLabel in DataMap["DataLabels"]:
      249         Writer.write("""          <th>%s</th>\n""" % DataLabel)
      250
      251     Writer.write("""        </tr>\n""")
      252     Writer.write("""      </thead>\n""")
      253
      254 def WriteTableFooterRow(Writer, ValidMols, DataMap):
      255     """Write out table footer row."""
      256
      257     if not OptionsInfo["TableFooter"]:
      258         return
      259
      260     Writer.write("""      <tfoot>\n""")
      261     Writer.write("""        <tr>\n""")
      262
      263     if OptionsInfo["CounterCol"]:
      264         Writer.write("""          <td></td>\n""")
      265     Writer.write("""          <td>Structure</td>\n""")
      266     if OptionsInfo["ShowMolName"]:
      267         Writer.write("""          <td>%s</td>\n""" % OptionsInfo["ShowMolNameDataLabel"])
      268
      269     # Write out rest of the column headers...
      270     for DataLabel in DataMap["DataLabels"]:
      271         Writer.write("""          <td>%s</td>\n""" % DataLabel)
      272
      273     Writer.write("""        </tr>\n""")
      274     Writer.write("""      </tfoot>\n""")
      275
      276 def WriteHTMLPageHeader(Writer, DataMap):
      277     """Write out HTML page header."""
      278
      279     # Collect column indices containing counter and structure data to disable
      280     # sorting and searching. In addition, set up a list to exclude counter and
      281     # primary structure columns from column visibility pulldown along with
      282     # any other columns...
      283     #
      284     if OptionsInfo["CounterCol"]:
      285         StrColIndicesList = ["0", "1"]
      286         ColVisibilityExcludeColIndicesList = ["0", "1"]
      287         ColIndexOffset = 2
      288         FreezeLeftColumns = "2"
      289     else:
      290         StrColIndicesList = ["0"]
      291         ColVisibilityExcludeColIndicesList = ["0"]
      292         ColIndexOffset = 1
      293         FreezeLeftColumns = "1"
      294
      295     if OptionsInfo["ShowMolName"]:
      296         ColIndexOffset += 1
      297
      298     MaxColVisColCount = OptionsInfo["ColVisibilityCtrlMax"]
      299     MaxDataColVisColCount = MaxColVisColCount - len(ColVisibilityExcludeColIndicesList)
      300     MaxDataColVisColCount = MaxColVisColCount
      301
      302     DataColVisibilityExclude = False
      303     ColCount = len(DataMap["DataLabels"])
      304     if OptionsInfo["ColVisibility"]:
      305         if ColCount > MaxDataColVisColCount:
      306             DataColVisibilityExclude = True
      307             MiscUtil.PrintWarning("The number of data columns, %d, is more than %d. Only first %d data columns will be available in column visibility pulldown." % (ColCount, MaxColVisColCount, MaxColVisColCount))
      308
      309     DisplayButtons = False
      310     if OptionsInfo["ColVisibility"]:
      311         if ColCount > 0 or OptionsInfo["ShowMolName"]:
      312             DisplayButtons = True
      313
      314     FreezeCols = False
      315     if (OptionsInfo["FreezeCols"] and OptionsInfo["ScrollX"]):
      316         FreezeCols = True
      317
      318     for Index, DataLabel in enumerate(DataMap["DataLabels"]):
      319         if DataMap["StructureDataMap"][DataLabel]:
      320             StrColIndex = Index + ColIndexOffset
      321             StrColIndicesList.append("%s" % StrColIndex)
      322
      323         if OptionsInfo["ColVisibility"]:
      324             if Index >= MaxDataColVisColCount:
      325                 ColIndex = Index + ColIndexOffset
      326                 ColVisibilityExcludeColIndicesList.append("%s" %ColIndex)
      327
      328     StrColIndices = MiscUtil.JoinWords(StrColIndicesList, ", ")
      329     ColVisibilityExcludeColIndices = MiscUtil.JoinWords(ColVisibilityExcludeColIndicesList, ", ")
      330
      331     Paging = "true" if OptionsInfo["Paging"] else "false"
      332     PageLength = "%d" % OptionsInfo["PageLength"]
      333     PagingType = "\"%s\"" % OptionsInfo["PagingType"]
      334
      335     ScrollX = "true" if OptionsInfo["ScrollX"] else "false"
      336
      337     ScrollY = ""
      338     if OptionsInfo["ScrollY"]:
      339         if re.search("vh$", OptionsInfo["ScrollYSize"]):
      340             ScrollY = "\"%s\"" % OptionsInfo["ScrollYSize"]
      341         else:
      342             ScrollY = "%s" % OptionsInfo["ScrollYSize"]
      343
      344     RegexSearch = "true" if  OptionsInfo["RegexSearch"] else "false"
      345
      346     # Start HTML header...
      347     Title = "Molecules and data table" if OptionsInfo["Header"] is None else OptionsInfo["Header"]
      348
      349     Writer.write("""\
      350 <!doctype html>
      351 <html lang="en">
      352 <head>
      353     <title>%s</title>
      354     <meta charset="utf-8">
      355     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      356     <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
      357     <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
      358
      359 """ % (Title))
      360
      361     if (FreezeCols):
      362         Writer.write("""\
      363     <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">
      364 """)
      365
      366     if (OptionsInfo["KeysNavigation"]):
      367         Writer.write("""\
      368     <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.3.2/css/keyTable.bootstrap4.min.css">
      369 """)
      370
      371     Writer.write("""\
      372
      373     <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
      374     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
      375     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
      376
      377 """)
      378
      379     if DisplayButtons:
      380         Writer.write("""\
      381     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
      382     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
      383     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.colVis.min.js"></script>
      384
      385 """)
      386
      387     if (FreezeCols):
      388         Writer.write("""\
      389     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>
      390 """)
      391
      392     if (OptionsInfo["KeysNavigation"]):
      393         Writer.write("""\
      394     <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/keytable/2.3.2/js/dataTables.keyTable.min.js"></script>
      395 """)
      396
      397     # Intialize table using Bootstrap, DataTables and JQuery frameworks...
      398     Writer.write("""\
      399
      400     <script type="text/javascript" class="init">
      401
      402 $(document).ready(function() {
      403     var MolsAndDataTable = $('#MolsAndDataTable').DataTable( {
      404         "columnDefs": [
      405             {
      406                 "orderable": false,
      407                 "searchable": false,
      408                 "targets": [%s]
      409             },
      410 """ % (StrColIndices))
      411
      412     if OptionsInfo["ColVisibility"]:
      413         Writer.write("""\
      414             {
      415                 "className": "noColVisCtrl",
      416                 "targets": [%s]
      417             }
      418 """ % (ColVisibilityExcludeColIndices))
      419
      420     Writer.write("""\
      421         ],
      422 """)
      423
      424     # Setup column visibility control pulldown by excluding counter column
      425     # and primary structure column from the list...
      426     #
      427     if OptionsInfo["ColVisibility"]:
      428         # Set up dom for button display...
      429         if OptionsInfo["Paging"]:
      430             Writer.write("""\
      431         "dom":  "<'row'<'col'l><'col'B><'col'f>>" +
      432             "<'row'<'col-sm-12'tr>>" +
      433             "<'row'<'col-sm-5'i><'col-sm-7'p>>",
      434 """)
      435         else:
      436             Writer.write("""\
      437         "dom":  "<'row'<'col-sm-6'B><'col-sm-6'f>>" +
      438             "<'row'<'col-sm-12'tr>>" +
      439             "<'row'<'col-sm-5'i><'col-sm-7'p>>",
      440 """)
      441         # Set up buttons...
      442         Writer.write("""\
      443         "buttons": [
      444             {
      445                 "extend": "colvis",
      446                 "text": "Column visibility",
      447                 "className": "btn btn-outline-light text-dark",
      448                 "columns": ":not(.noColVisCtrl)",
      449 """)
      450         if not DataColVisibilityExclude:
      451             Writer.write("""\
      452                 "prefixButtons": [ "colvisRestore" ],
      453 """)
      454
      455         Writer.write("""\
      456                 "columnText": function ( dt, colIndex, colLabel ) {
      457                     return (colIndex + 1) + ": " + colLabel;
      458                 },
      459             }
      460         ],
      461 """)
      462
      463     # Write out rest of the variables for DataTables...
      464     if FreezeCols:
      465         Writer.write("""\
      466         "fixedColumns": {
      467             "leftColumns": %s
      468         },
      469 """ % (FreezeLeftColumns))
      470
      471     if (OptionsInfo["KeysNavigation"]):
      472         Writer.write("""\
      473         "keys": true,
      474 """)
      475
      476     Writer.write("""\
      477         "pageLength": %s,
      478         "lengthMenu": [ [10, 15, 25, 50, 100, 500, 1000, -1], [10, 15, 25, 50, 100, 500, 1000, "All"] ],
      479         "paging": %s,
      480         "pagingType": %s,
      481         "scrollX": %s,
      482         "scrollY": %s,
      483         "scrollCollapse": true,
      484         "order": [],
      485         "search" : {"regex" : %s},
      486     } );
      487 """ % (PageLength, Paging, PagingType, ScrollX, ScrollY, RegexSearch))
      488
      489     if OptionsInfo["CounterCol"]:
      490         Writer.write("""\
      491     MolsAndDataTable.on( 'order.dt search.dt', function () {
      492         MolsAndDataTable.column(0, {search:'applied', order:'applied'}).nodes().each( function (cell, rowIndex) {
      493             cell.innerHTML = rowIndex + 1;
      494         } );
      495     } ).draw();
      496 """)
      497
      498     # End of Javacscript code...
      499     Writer.write("""\
      500 } );
      501
      502     </script>
      503 """)
      504
      505     # Finish up HTML header...
      506     Writer.write("""\
      507
      508 </head>
      509 <body>
      510   <div class="container-fluid">
      511     <br/>
      512 """)
      513
      514 def WriteHTMLPageEnd(Writer):
      515     """Write out HTML page end."""
      516
      517     Writer.write("""\
      518   </div>
      519 </body>
      520 </html>
      521 """)
      522
      523 def WriteHTMLPageTitle(Writer):
      524     """Write out HTML page title."""
      525
      526     if OptionsInfo["Header"] is None:
      527         return
      528
      529     Writer.write("""    <%s class="text-center">%s</%s>\n""" % (OptionsInfo["HeaderStyle"], OptionsInfo["Header"], OptionsInfo["HeaderStyle"]))
      530
      531 def WriteHTMLPageFooter(Writer):
      532     """Write out HTML page footer."""
      533
      534     if OptionsInfo["Footer"] is None:
      535         return
      536
      537     Writer.write("""    <br/>\n    <p class="%s">%s</p>\n""" % (OptionsInfo["FooterClass"], OptionsInfo["Footer"]))
      538
      539 def WriteHTMLTableHeader(Writer):
      540     """Write out HTML table header."""
      541
      542     if OptionsInfo["TableStyle"] is None:
      543         Writer.write("""\n    <table id="MolsAndDataTable" cellspacing="0" width="100%">\n""")
      544     else:
      545         Writer.write("""    <table id="MolsAndDataTable" class="%s" cellspacing="0" width="100%s">\n""" % (OptionsInfo["TableStyle"], "%"))
      546
      547 def WriteHTMLTableEnd(Writer):
      548     """Write out HTML table end."""
      549
      550     Writer.write("""    </table>\n\n""")
      551
      552 def RetrieveMoleculesAndData():
      553     """Retrieve molecules and data from input file."""
      554
      555     MiscUtil.PrintInfo("\nReading file %s..." % OptionsInfo["Infile"])
      556
      557     if MiscUtil.CheckFileExt(OptionsInfo["Infile"] ,"smi csv tsv txt"):
      558         # Check for the presence of SMILES column name in title line...
      559         Infile = open(OptionsInfo["Infile"], "r")
      560         if Infile is None:
      561             MiscUtil.PrintError("Couldn't open file %s..." % OptionsInfo["Infile"])
      562         Line = Infile.readline()
      563         Infile.close()
      564
      565         if not re.search("SMILES", Line, re.I):
      566             MiscUtil.PrintError("The input file, %s, must contain a title line containing a column name with SMILES in its name." % OptionsInfo["Infile"])
      567
      568     if MiscUtil.CheckFileExt(OptionsInfo["Infile"],"sdf sd smi"):
      569         ValidMols, MolCount, ValidMolCount  = RDKitUtil.ReadAndValidateMolecules(OptionsInfo["Infile"], **OptionsInfo["InfileParams"])
      570     else:
      571         ValidMols, MolCount, ValidMolCount  = RetrieveMoleculesFromTextFile(OptionsInfo["Infile"])
      572
      573     MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount)
      574     MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
      575     MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
      576
      577     return ValidMols
      578
      579 def RetrieveMoleculesFromTextFile(Infile):
      580     """Retrieve molecules from a CSV/TSV text file."""
      581
      582     # Read and parse text lines...
      583     Delimiter = "," if MiscUtil.CheckFileExt(Infile ,"csv") else "\t"
      584     QuoteChar = '"'
      585     IgnoreHeaderLine = False
      586     TextLinesWords = MiscUtil.GetTextLinesWords(Infile, Delimiter, QuoteChar, IgnoreHeaderLine)
      587
      588     # Process column names...
      589     ColNames = TextLinesWords[0]
      590     ColCount = len(ColNames)
      591
      592     MolColIndex = None
      593     MolDataColIndices = []
      594
      595     FirstSMILES = True
      596     for ColIndex in range(0, ColCount):
      597         if re.search("SMILES", ColNames[ColIndex], re.I) and FirstSMILES:
      598             MolColIndex = ColIndex
      599             FirstSMILES = False
      600             continue
      601
      602         MolDataColIndices.append(ColIndex)
      603
      604     if MolColIndex is None:
      605             MiscUtil.PrintError("The input file, %s, must contain a title line containing a column name with SMILES in its name." % Infile)
      606
      607     ValidMols = []
      608     MolCount = 0
      609
      610     Sanitize = OptionsInfo["InfileParams"]["Sanitize"]
      611
      612     # Process data lines...
      613     for LineIndex in range(1, len(TextLinesWords)):
      614         MolCount += 1
      615         LineWords = TextLinesWords[LineIndex]
      616         if len(LineWords) != ColCount:
      617             MiscUtil.PrintWarning("Ignoring text line number %d: Number of columns, %d, must match number of columns, %d, in title line.\nLine: %s" % (MolCount, len(LineWords), ColCount, Delimiter.join(LineWords)))
      618             continue
      619
      620         # Process molecule column...
      621         MolSMILES = LineWords[MolColIndex]
      622         Mol = Chem.MolFromSmiles(MolSMILES, sanitize = Sanitize)
      623         if Mol is None:
      624             MiscUtil.PrintWarning("Ignoring text line number %d: SMILES parsing failed\nLine: %s" % (MolCount, Delimiter.join(LineWords)))
      625             continue
      626
      627         # Process molecule data columns...
      628         for ColIndex in MolDataColIndices:
      629             Name = ColNames[ColIndex]
      630             Value = LineWords[ColIndex]
      631             Mol.SetProp(Name, Value)
      632
      633         ValidMols.append(Mol)
      634
      635     ValidMolCount = len(ValidMols)
      636
      637     return (ValidMols, MolCount, ValidMolCount)
      638
      639 def IdentifyStructureAndNumericalData(ValidMols):
      640     """Identify structure and alphanumerical data."""
      641
      642     DataMap = {}
      643     DataMap["DataLabels"] = []
      644     DataMap["DataLabelsMap"] = {}
      645     DataMap["CanonicalDataLabelsMap"] = {}
      646
      647     DataMap["StructureDataMap"] = {}
      648     DataMap["SMILESDataMap"] = {}
      649
      650     # Retrieve all possible data labels...
      651     if MiscUtil.CheckFileExt(OptionsInfo["Infile"] ,"smi csv tsv txt"):
      652         # First molecule contains all possible data fields...
      653         Mol = ValidMols[0]
      654         ProcessMolDataLabels(ValidMols[0], DataMap)
      655     else:
      656         # Go over all molecules to identify unique data labels...
      657         MiscUtil.PrintInfo("\nRetrieving unique data labels for data in file %s..." % OptionsInfo["Infile"])
      658         for Mol in ValidMols:
      659             ProcessMolDataLabels(Mol, DataMap)
      660
      661     return DataMap
      662
      663 def ProcessMolDataLabels(Mol, DataMap):
      664     """Process data label to identify and track its type"""
      665
      666     for DataLabel in Mol.GetPropNames(includePrivate = False, includeComputed = False):
      667         if DataLabel in DataMap["DataLabelsMap"]:
      668             continue
      669
      670         # Track labels...
      671         DataMap["DataLabels"].append(DataLabel)
      672         DataMap["DataLabelsMap"][DataLabel] = DataLabel
      673         DataMap["CanonicalDataLabelsMap"][DataLabel.lower()] = DataLabel
      674
      675         DataMap["StructureDataMap"][DataLabel] = False
      676         DataMap["SMILESDataMap"][DataLabel] = False
      677
      678         if re.search("SMILES", DataLabel, re.I):
      679             DataMap["StructureDataMap"][DataLabel] = True
      680             DataMap["SMILESDataMap"][DataLabel] = True
      681
      682 def ValidateShowMolNameOption(DataMap):
      683     """Validate show molecule name option. """
      684
      685     if not OptionsInfo["ShowMolName"]:
      686         return
      687
      688     if not MiscUtil.CheckFileExt(OptionsInfo["Infile"],"sdf sd smi"):
      689         OptionsInfo["ShowMolName"] = False
      690         return
      691
      692     CanonicalDataLabel = OptionsInfo["ShowMolNameDataLabel"].lower()
      693     if CanonicalDataLabel in DataMap["CanonicalDataLabelsMap"]:
      694         OptionsInfo["ShowMolName"] = False
      695         if not OptionsInfo["ShowMolNameAuto"]:
      696             MiscUtil.PrintWarning("Ignoring \"--showMolName\" option: Data label \"Name\" corresponding to molecule name is already present in input file.")
      697
      698 def ValidateSpecifiedDataLabels(DataMap):
      699     """Validate data labels used to specify highlighting data. """
      700
      701     ValidateSpecifiedDataLabelsForHighlightSMARTS(DataMap)
      702
      703     ValidateSpecifiedDataLabelsForHighlightValues(DataMap)
      704     ValidateSpecifiedDataLabelsForHighlightRanges(DataMap)
      705     ValidateSpecifiedDataLabelsForHighlightClasses(DataMap)
      706
      707 def ValidateSpecifiedDataLabelsForHighlightSMARTS(DataMap):
      708     """Validate data labels used to specify highlighting SMARTS option. """
      709
      710     if OptionsInfo["HighlightSMARTSAllMode"]:
      711         return
      712
      713     for DataLabel in OptionsInfo["HighlightSMARTSDataLabels"]:
      714         if re.match("^Structure$", DataLabel, re.I):
      715             continue
      716
      717         CanonicalDataLabel = DataLabel.lower()
      718         if not CanonicalDataLabel in DataMap["CanonicalDataLabelsMap"]:
      719             MiscUtil.PrintError("The data label specified, %s, using option \"--highlightSMARTS\" doesn't exist in input file." % DataLabel)
      720
      721         Label = DataMap["CanonicalDataLabelsMap"][CanonicalDataLabel]
      722         if not DataMap["StructureDataMap"][Label]:
      723             MiscUtil.PrintError("The data label specified, %s, using option \"--highlightSMARTS\" doesn't correspond to structure data: Valid structure data labels: SMILES in data label." % DataLabel)
      724
      725 def ValidateSpecifiedDataLabelsForHighlightValues(DataMap):
      726     """Validate data labels used to specify highlighting values option. """
      727
      728     ValidateDataLabels("--highlightValues", DataMap, OptionsInfo["HighlightValuesLabels"])
      729
      730 def ValidateSpecifiedDataLabelsForHighlightRanges(DataMap):
      731     """Validate data labels used to specify highlighting ranges option. """
      732
      733     ValidateDataLabels("--highlightRanges", DataMap, OptionsInfo["HighlightRangesLabels"])
      734
      735 def ValidateSpecifiedDataLabelsForHighlightClasses(DataMap):
      736     """Validate data labels used to specify highlighting classes option. """
      737
      738     if OptionsInfo["HighlightClassesRules"] is None:
      739         return
      740
      741     ValidDataLabelsList = []
      742     NotValidDataLabelsList = []
      743     for Label in OptionsInfo["HighlightClassesLabels"]:
      744         ValidCanonicalLabel = None
      745
      746         for LabelSynonym in OptionsInfo["HighlightClassesSynonymsMap"][Label]:
      747             CanonicalLabel = LabelSynonym.lower()
      748
      749             # Is this label already in use...
      750             if CanonicalLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
      751                 MiscUtil.PrintInfo("")
      752                 MiscUtil.PrintWarning("The data label, %s, for class, %s , in option \"--highlightValuesClasses\" has already been used in \"--highlightValues\" option. It'll be ignored during highlighting." % (LabelSynonym, OptionsInfo["HighlightClasses"]))
      753                 continue
      754
      755             if CanonicalLabel in OptionsInfo["HighlightRangesCanonicalLabelsMap"]:
      756                 MiscUtil.PrintInfo("")
      757                 MiscUtil.PrintWarning("The data label, %s, for class, %s , in option \"--highlightValuesClasses\" has already been used in \"--highlightValuesRanges\" option. It'll be ignored during highlighting." % (LabelSynonym, OptionsInfo["HighlightClasses"]))
      758                 continue
      759
      760             # Is this label present in input file...
      761             if  CanonicalLabel in DataMap["CanonicalDataLabelsMap"]:
      762                 ValidCanonicalLabel = CanonicalLabel
      763                 break
      764
      765         if ValidCanonicalLabel is None:
      766             MiscUtil.PrintWarning("The data label or its synonyms - %s - for class, %s , in option \"--highlightValuesClasses\" either don't exist in input file or have already been used for highlighting in option \"--highlightValuesClasses\" or \"--highlightValuesRanges\". It'll be ignored during highlighting." % (MiscUtil.JoinWords(OptionsInfo["HighlightClassesSynonymsMap"][Label], ", "), OptionsInfo["HighlightClasses"]))
      767             NotValidDataLabelsList.append(Label)
      768             continue
      769
      770         # Track label...
      771         OptionsInfo["HighlightClassesCanonicalLabelsMap"][ValidCanonicalLabel] = Label
      772         ValidDataLabelsList.append(DataMap["CanonicalDataLabelsMap"][ValidCanonicalLabel])
      773
      774     ValidDataLabelsCount = len(ValidDataLabelsList)
      775     NotValidDataLabelsCount = len(NotValidDataLabelsList)
      776     DataLabelsCount = len(OptionsInfo["HighlightClassesLabels"])
      777
      778     if ValidDataLabelsCount == 0:
      779         MiscUtil.PrintInfo("")
      780         MiscUtil.PrintWarning("The data labels and their synonyms for class, %s , in option \"--highlightValuesClasses\" either don't exists in input file or have already been used for highlighting in option \"--highlightValuesClasses\" or \"--highlightValuesRanges\". No class highlighting will be performed. Missing data labels:  %s" % (OptionsInfo["HighlightClasses"], MiscUtil.JoinWords(OptionsInfo["HighlightClassesLabels"], ", ")))
      781     elif  ValidDataLabelsCount < DataLabelsCount:
      782         MiscUtil.PrintInfo("")
      783         MiscUtil.PrintWarning("The class, %s, based highlighting specified using \"--highlightValuesClasses\" option will be performed using only, %d, out of, %d, data labels: %s\nThe rest of the data label(s) - %s - either don't exist in the input file or have aready been used for highlighting in option \"--highlightValuesClasses\" or \"--highlightValuesRanges\"." % (OptionsInfo["HighlightClasses"], ValidDataLabelsCount,  DataLabelsCount,  MiscUtil.JoinWords(ValidDataLabelsList, ", "),  MiscUtil.JoinWords(NotValidDataLabelsList, ", ") ))
      784
      785 def ValidateDataLabels(OptionName, DataMap, DataLabels):
      786     """Validate data labels."""
      787
      788     for DataLabel in DataLabels:
      789         if re.match("^Structure$", DataLabel, re.I):
      790             MiscUtil.PrintError("The data label specified, %s, using option \"-%s\" must not correspond to structure data. Structure label is not allowed." % (DataLabel, OptionName))
      791
      792         CanonicalDataLabel = DataLabel.lower()
      793         if not CanonicalDataLabel in DataMap["CanonicalDataLabelsMap"]:
      794             MiscUtil.PrintError("The data label specified, %s, using option \"%s\" doesn't exist in input file." % (DataLabel, OptionName))
      795
      796         Label = DataMap["CanonicalDataLabelsMap"][CanonicalDataLabel]
      797         if DataMap["StructureDataMap"][Label]:
      798             MiscUtil.PrintError("The data label specified, %s, using option \"%s\" must not correspond to structure data: Valid structure data labels contain \"SMILES\" in their name.." % (DataLabel, OptionName))
      799
      800 def SetupMolInLineSVGImageTag(Mol, HightlightAtomList):
      801     """Setup a inline SVG image tag for molecule."""
      802
      803     SVGText = RDKitUtil.GetInlineSVGForMolecule(Mol, OptionsInfo["MolImageWidth"], OptionsInfo["MolImageHeight"], AtomListToHighlight = HightlightAtomList)
      804     SVGInlineImageTag = "img src=\"data:image/svg+xml;charset=UTF-8,\n%s\"" % SVGText
      805
      806     return SVGInlineImageTag
      807
      808 def SetupAtomListToHighlight(Mol, DataLabel):
      809     """Set up atom list to highlight using specified SMARTS patterns."""
      810
      811     HighlightAtomList = None
      812     if OptionsInfo["HighlightSMARTS"] is None:
      813         return  HighlightAtomList
      814
      815     if OptionsInfo["HighlightSMARTSAllMode"]:
      816         PatternMol = OptionsInfo["HighlightSMARTSPatternMol"]
      817     else:
      818         CanonicalDataLabel = DataLabel.lower()
      819         if not CanonicalDataLabel in OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"]:
      820             return  HighlightAtomList
      821
      822         Label = OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"][CanonicalDataLabel]
      823         PatternMol = OptionsInfo["HighlightSMARTSPatternMolsMap"][Label]
      824
      825     # Get matched atom lists and flatten it...
      826     MatchedAtomsLists = Mol.GetSubstructMatches(PatternMol)
      827     MatchedAtoms = [ Atom for AtomsList in MatchedAtomsLists for Atom in AtomsList]
      828
      829     if len(MatchedAtoms):
      830         HighlightAtomList = MatchedAtoms
      831
      832     return  HighlightAtomList
      833
      834 def GetAlphanumeircValueHighlightBackgroundColor(DataLabel, DataValue, DataMap):
      835     """Get background highlight color for a value."""
      836
      837     BackgroundColor = None
      838     BackgroundColorType = None
      839
      840     CanonicalDataLabel =DataLabel.lower()
      841     if CanonicalDataLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
      842         return GetBackgroundColorUsingHighlightValuesMode(DataLabel, DataValue, DataMap)
      843     elif CanonicalDataLabel in OptionsInfo["HighlightRangesCanonicalLabelsMap"]:
      844         return GetBackgroundColorUsingHighlightRangesMode(DataLabel, DataValue, DataMap)
      845     elif CanonicalDataLabel in OptionsInfo["HighlightClassesCanonicalLabelsMap"]:
      846         return GetBackgroundColorUsingHighlightClassesMode(DataLabel, DataValue, DataMap)
      847     elif OptionsInfo["HighlightClassesRandom"]:
      848         return GetBackgroundColorUsingRandomMode(DataLabel, DataValue, DataMap)
      849
      850     return (BackgroundColor, BackgroundColorType)
      851
      852 def GetBackgroundColorUsingHighlightValuesMode(DataLabel, DataValue, DataMap):
      853     """Get background highlight color for a value."""
      854
      855     BackgroundColor = None
      856     BackgroundColorType = None
      857
      858     CanonicalDataLabel =DataLabel.lower()
      859     if not CanonicalDataLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
      860         return (BackgroundColor, BackgroundColorType)
      861
      862     Label = OptionsInfo["HighlightValuesCanonicalLabelsMap"][CanonicalDataLabel]
      863     DataType = OptionsInfo["HighlightValuesTypesMap"][Label]
      864     Criterion = OptionsInfo["HighlightValuesCriteriaMap"][Label]
      865     CriterionValue = OptionsInfo["HighlightValuesCriteriaValuesMap"][Label]
      866
      867     return GetBackgroundColorForHighlightingValue(DataLabel, DataValue, DataType, Criterion, CriterionValue)
      868
      869 def GetBackgroundColorUsingHighlightClassesMode(DataLabel, DataValue, DataMap):
      870     """Get background highlight color for a value."""
      871
      872     BackgroundColor = None
      873     BackgroundColorType = None
      874
      875     CanonicalDataLabel =DataLabel.lower()
      876     if not CanonicalDataLabel in OptionsInfo["HighlightClassesCanonicalLabelsMap"]:
      877         return (BackgroundColor, BackgroundColorType)
      878
      879     Label = OptionsInfo["HighlightClassesCanonicalLabelsMap"][CanonicalDataLabel]
      880     DataType = OptionsInfo["HighlightClassesTypesMap"][Label]
      881     Criterion = OptionsInfo["HighlightClassesCriteriaMap"][Label]
      882     CriterionValue = OptionsInfo["HighlightClassesCriteriaValuesMap"][Label]
      883
      884     return GetBackgroundColorForHighlightingValue(DataLabel, DataValue, DataType, Criterion, CriterionValue)
      885
      886 def GetBackgroundColorForHighlightingValue(DataLabel, DataValue, DataType, Criterion, CriterionValue):
      887     """Get background color for highlighting a value."""
      888
      889     ValueOkay = False
      890     if re.match("^numeric$", DataType, re.I):
      891         if not MiscUtil.IsNumber(DataValue):
      892             MiscUtil.PrintWarning("Ignoring data value, %s, for data label, %s, during numeric highlighting: It must be a number" % (DataValue, DataLabel))
      893             return (BackgroundColor, BackgroundColorType)
      894
      895         DataValue = float(DataValue)
      896         if re.match("^gt$", Criterion, re.I):
      897             ValueOkay = True if DataValue > CriterionValue else False
      898         elif re.match("^lt$", Criterion, re.I):
      899             ValueOkay = True if DataValue < CriterionValue else False
      900         elif re.match("^ge$", Criterion, re.I):
      901             ValueOkay = True if DataValue >= CriterionValue else False
      902         elif re.match("^le$", Criterion, re.I):
      903             ValueOkay = True if DataValue <= CriterionValue else False
      904         elif re.match("^eq$", Criterion, re.I):
      905             ValueOkay = True if DataValue == CriterionValue else False
      906         elif re.match("^ne$", Criterion, re.I):
      907             ValueOkay = True if DataValue != CriterionValue else False
      908         else:
      909             return (BackgroundColor, BackgroundColorType)
      910     elif re.match("^text$", DataType, re.I):
      911         DataValue = "%s" % DataValue
      912         if re.match("^gt$", Criterion, re.I):
      913             ValueOkay = True if DataValue > CriterionValue else False
      914         elif re.match("^lt$", Criterion, re.I):
      915             ValueOkay = True if DataValue < CriterionValue else False
      916         elif re.match("^ge$", Criterion, re.I):
      917             ValueOkay = True if DataValue >= CriterionValue else False
      918         elif re.match("^le$", Criterion, re.I):
      919             ValueOkay = True if DataValue <= CriterionValue else False
      920         elif re.match("^eq$", Criterion, re.I):
      921             ValueOkay = True if DataValue == CriterionValue else False
      922         elif re.match("^ne$", Criterion, re.I):
      923             ValueOkay = True if DataValue != CriterionValue else False
      924         else:
      925             return (BackgroundColor, BackgroundColorType)
      926     elif re.match("^regex$", DataType, re.I):
      927         DataValue = "%s" % DataValue
      928         if re.match("^eq$", Criterion, re.I):
      929             ValueOkay = True if re.search("%s" % CriterionValue, DataValue, re.I) else False
      930         elif re.match("^ne$", Criterion, re.I):
      931             ValueOkay = False if re.search("%s" % CriterionValue, DataValue, re.I) else True
      932         else:
      933             return (BackgroundColor, BackgroundColorType)
      934
      935     BackgroundColor = OptionsInfo["HighlightColorsList"][0] if ValueOkay else OptionsInfo["HighlightColorsList"][1]
      936     BackgroundColorType = OptionsInfo["HighlightColorsType"]
      937
      938     return (BackgroundColor, BackgroundColorType)
      939
      940 def GetBackgroundColorUsingHighlightRangesMode(DataLabel, DataValue, DataMap):
      941     """Get background highlight color for value range."""
      942
      943     BackgroundColor = None
      944     BackgroundColorType = None
      945
      946     CanonicalDataLabel =DataLabel.lower()
      947     if not CanonicalDataLabel in OptionsInfo["HighlightRangesCanonicalLabelsMap"]:
      948         return (BackgroundColor, BackgroundColorType)
      949
      950     Label = OptionsInfo["HighlightRangesCanonicalLabelsMap"][CanonicalDataLabel]
      951     DataType = OptionsInfo["HighlightRangesTypesMap"][Label]
      952     CriterionLower = OptionsInfo["HighlightRangesCriteriaLowerMap"][Label]
      953     CriterionLowerValue = OptionsInfo["HighlightRangesCriteriaLowerValuesMap"][Label]
      954     CriterionUpper = OptionsInfo["HighlightRangesCriteriaUpperMap"][Label]
      955     CriterionUpperValue = OptionsInfo["HighlightRangesCriteriaUpperValuesMap"][Label]
      956
      957     if re.match("^numeric$", DataType, re.I):
      958         if not MiscUtil.IsNumber(DataValue):
      959             MiscUtil.PrintWarning("Ignoring data value, %s, for data label, %s, during numeric highlighting: It must be a number" % (DataValue, DataLabel))
      960             return (BackgroundColor, BackgroundColorType)
      961
      962         DataValue = float(DataValue)
      963         ColorIndex = 1
      964
      965         if DataValue < CriterionLowerValue and re.match("^lt$", CriterionLower, re.I):
      966             ColorIndex = 0
      967         elif DataValue <= CriterionLowerValue and re.match("^le$", CriterionLower, re.I):
      968             ColorIndex = 0
      969         elif DataValue > CriterionUpperValue and re.match("^gt$", CriterionUpper, re.I):
      970             ColorIndex = 2
      971         elif DataValue >= CriterionUpperValue and re.match("^ge$", CriterionUpper, re.I):
      972             ColorIndex = 2
      973     elif re.match("^text$", DataType, re.I):
      974         DataValue = "%s" % DataValue
      975         ColorIndex = 1
      976
      977         if DataValue < CriterionLowerValue and re.match("^lt$", CriterionLower, re.I):
      978             ColorIndex = 0
      979         elif DataValue <= CriterionLowerValue and re.match("^le$", CriterionLower, re.I):
      980             ColorIndex = 0
      981         elif DataValue > CriterionUpperValue and re.match("^gt$", CriterionUpper, re.I):
      982             ColorIndex = 2
      983         elif DataValue >= CriterionUpperValue and re.match("^ge$", CriterionUpper, re.I):
      984             ColorIndex = 2
      985     else:
      986         return (BackgroundColor, BackgroundColorType)
      987
      988     BackgroundColor = OptionsInfo["HighlightColorsRangesList"][ColorIndex]
      989     BackgroundColorType = OptionsInfo["HighlightColorsRangesType"]
      990
      991     return (BackgroundColor, BackgroundColorType)
      992
      993 def GetBackgroundColorUsingRandomMode(DataLabel, DataValue, DataMap):
      994     """Get a random background highlight color for a value."""
      995
      996     BackgroundColor = random.choice(OptionsInfo["HighlightColorsRandomList"])
      997     BackgroundColorType = OptionsInfo["HighlightColorsRandomType"]
      998
      999     return (BackgroundColor, BackgroundColorType)
     1000
     1001 def SetupAlphanumericTableDataValue(Writer, DataValue, BackgroundColor, BackgroundColorType):
     1002     """Set up alphanumeric data value for a table cell."""
     1003
     1004     WrappedDataValue = "%s" % DataValue
     1005
     1006     # Look for new lines...
     1007     Delim = "<br/>"
     1008     if re.search("(\r\n|\r|\n)", WrappedDataValue):
     1009         WrappedDataValue = re.sub("(\r\n|\r|\n)", "<br/>", DataValue)
     1010
     1011     # Wrap text...
     1012     if OptionsInfo["WrapText"] and len(WrappedDataValue) > OptionsInfo["WrapTextWidth"]:
     1013         WrappedDataLines = []
     1014         for DataLine in WrappedDataValue.split("<br/>"):
     1015             WrappedDataLine = MiscUtil.WrapText(DataLine, "<br/>", OptionsInfo["WrapTextWidth"])
     1016             WrappedDataLines.append(WrappedDataLine)
     1017
     1018         WrappedDataValue = "<br/>".join(WrappedDataLines)
     1019
     1020     # Highlight value...
     1021     if BackgroundColor is not None:
     1022         ColorTypeTag = GetBackgroundColorTypeTagForTableValue(BackgroundColor, BackgroundColorType)
     1023         Writer.write("""            <td %s = "%s">%s</td>\n""" % (ColorTypeTag, BackgroundColor, WrappedDataValue))
     1024     else:
     1025         Writer.write("""            <td>%s</td>\n""" % WrappedDataValue)
     1026
     1027 def GetBackgroundColorTypeTagForTableValue(Color, ColorType):
     1028     """Setup color type tage for setting background of a table value."""
     1029
     1030     ColorTypeTag = "class" if re.match("^colorclass", ColorType, re.I) else "bgcolor"
     1031
     1032     return ColorTypeTag
     1033
     1034 def PerformAlignment(ValidMols):
     1035     """Perform alignment to a common template specified by a SMARTS pattern."""
     1036
     1037     if OptionsInfo["AlignmentSMARTSPattern"] is None:
     1038         return
     1039
     1040     MiscUtil.PrintInfo("\nPerforming alignment for primary structure data...")
     1041
     1042     PatternMol = Chem.MolFromSmarts(OptionsInfo["AlignmentSMARTSPattern"])
     1043     AllChem.Compute2DCoords(PatternMol)
     1044
     1045     MatchedValidMols = [ValidMol for ValidMol in ValidMols if ValidMol.HasSubstructMatch(PatternMol)]
     1046     for ValidMol in MatchedValidMols:
     1047         AllChem.GenerateDepictionMatching2DStructure(ValidMol, PatternMol)
     1048
     1049 def ProcessHighlightSMARTSOption():
     1050     """Process highlight SMARTS option"""
     1051
     1052     OptionsInfo["HighlightSMARTS"]  = None
     1053     OptionsInfo["HighlightSMARTSAllMode"]  = False
     1054     OptionsInfo["HighlightSMARTSPatternMol"]  = None
     1055
     1056     OptionsInfo["HighlightSMARTSDataLabels"]  = []
     1057     OptionsInfo["HighlightSMARTSDataLabelsMap"]  = {}
     1058
     1059     OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"]  = {}
     1060     OptionsInfo["HighlightSMARTSPatternsMap"]  = {}
     1061     OptionsInfo["HighlightSMARTSPatternMolsMap"]  = {}
     1062
     1063     if re.match("^None$", Options["--highlightSMARTS"], re.I):
     1064         # Nothing to proecess...
     1065         return
     1066
     1067     HighlightSMARTS = Options["--highlightSMARTS"].strip()
     1068     if not HighlightSMARTS:
     1069         MiscUtil.PrintError("No valid values specified using \"--highlightSMARTS\" option.")
     1070
     1071     OptionsInfo["HighlightSMARTS"]  = HighlightSMARTS
     1072     HighlightSMARTSWords = HighlightSMARTS.split(",")
     1073
     1074     if len(HighlightSMARTSWords) == 1:
     1075         PatternMol = Chem.MolFromSmarts(HighlightSMARTS)
     1076         if PatternMol is None:
     1077             MiscUtil.PrintError("The value specified, %s, using option \"--highlightSMARTS\" is not a valid SMARTS: Failed to create pattern molecule" % Options["--highlightSMARTS"])
     1078         OptionsInfo["HighlightSMARTSAllMode"]  = True
     1079         OptionsInfo["HighlightSMARTSPatternMol"]  = PatternMol
     1080         return
     1081
     1082     if len(HighlightSMARTSWords) % 2:
     1083         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"--highlightSMARTS\" option must be an even number." % (len(HighlightSMARTSWords)))
     1084
     1085     HighlightSMARTSAllMode = False
     1086
     1087     for Index in range(0, len(HighlightSMARTSWords), 2):
     1088         DataLabel = HighlightSMARTSWords[Index].strip()
     1089         SMARTSPattern = HighlightSMARTSWords[Index + 1].strip()
     1090
     1091         PatternMol = Chem.MolFromSmarts(SMARTSPattern)
     1092         if PatternMol is None:
     1093             MiscUtil.PrintError("The value specified, %s, using option \"--highlightSMARTS\" is not a valid SMARTS: Failed to create pattern molecule" % Options["--highlightSMARTS"])
     1094
     1095         if DataLabel in OptionsInfo["HighlightSMARTSDataLabelsMap"]:
     1096             MiscUtil.PrintError("The datalabel, %s, specified in pair, \"%s, %s\", using option \"--highlightSMARTS\" is not a valid: Multiple occurences of data label" % (DataLabel, DataLabel, SMARTSPattern))
     1097
     1098         OptionsInfo["HighlightSMARTSDataLabels"].append(DataLabel)
     1099         OptionsInfo["HighlightSMARTSDataLabelsMap"][DataLabel] = DataLabel
     1100         OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"][DataLabel.lower()] = DataLabel
     1101         OptionsInfo["HighlightSMARTSPatternsMap"][DataLabel] = SMARTSPattern
     1102         OptionsInfo["HighlightSMARTSPatternMolsMap"][DataLabel] = PatternMol
     1103
     1104 def ProcessHighlightDataOptions():
     1105     """Process highlight values and colors option"""
     1106
     1107     ProcessHighlightValuesOption()
     1108     ProcessHighlightValuesRangesOption()
     1109     ProcessHighlightValuesClassesOption()
     1110
     1111     ProcessHighlightColorsOption()
     1112     ProcessHighlightColorsRangesOption()
     1113     ProcessHighlightColorsRandomOption()
     1114
     1115 def ProcessHighlightValuesOption():
     1116     """Process highlight values option"""
     1117
     1118     OptionsInfo["HighlightValues"]  = None
     1119     OptionsInfo["HighlightValuesLabels"]  = []
     1120
     1121     OptionsInfo["HighlightValuesLabelsMap"]  = {}
     1122     OptionsInfo["HighlightValuesCanonicalLabelsMap"]  = {}
     1123
     1124     OptionsInfo["HighlightValuesTypesMap"]  = {}
     1125     OptionsInfo["HighlightValuesCriteriaMap"]  = {}
     1126     OptionsInfo["HighlightValuesCriteriaValuesMap"]  = {}
     1127
     1128     HighlightValues = Options["--highlightValues"].strip()
     1129     if re.match("^None$", HighlightValues, re.I):
     1130         return
     1131
     1132     OptionsInfo["HighlightValues"]  = HighlightValues
     1133     HighlightValuesWords = HighlightValues.split(",")
     1134
     1135     if len(HighlightValuesWords) % 4:
     1136         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"--highlightValues\" option must be a multiple of 4." % (len(HighlightValuesWords)))
     1137
     1138     for Index in range(0, len(HighlightValuesWords), 4):
     1139         DataLabel = HighlightValuesWords[Index].strip()
     1140         DataType = HighlightValuesWords[Index + 1].strip()
     1141         DataCriterion = HighlightValuesWords[Index + 2].strip()
     1142         DataValue = HighlightValuesWords[Index + 3].strip()
     1143
     1144         if not re.match("^(numeric|text|regex)$", DataType, re.I):
     1145             MiscUtil.PrintError("The data type, %s, specified in quratet \"%s,%s,%s,%s\", using \"--highlightValues\" option is not valid. Supported values: numeric, regex or text." % (DataType, DataLabel, DataType, DataCriterion, DataValue))
     1146
     1147         if re.match("^regex$", DataType, re.I):
     1148             if not re.match("^(eq|ne)$", DataCriterion, re.I):
     1149                 MiscUtil.PrintError("The data criterion, %s, specified in quratet \"%s,%s,%s,%s\", using \"--highlightValues\" option is not valid. Supported values: eq or ne" % (DataType, DataLabel, DataType, DataCriterion, DataValue))
     1150         else:
     1151             if not re.match("^(gt|lt|ge|le|eq|ne)$", DataCriterion, re.I):
     1152                 MiscUtil.PrintError("The data criterion, %s, specified in quratet \"%s,%s,%s,%s\", using \"--highlightValues\" option is not valid. Supported values: gt, lt, ge, le, eq, or  ne." % (DataType, DataLabel, DataType, DataCriterion, DataValue))
     1153
     1154         # Check criterion value...
     1155         if re.match("^numeric$", DataType, re.I):
     1156             if not MiscUtil.IsNumber(DataValue):
     1157                 MiscUtil.PrintError("The data value, %s, specified in quratet \"%s,%s,%s,%s\", using \"--highlightValues\" option is not valid. It must be a number for data type, %s" % (DataType, DataLabel, DataType, DataCriterion, DataValue, DataType))
     1158             DataValue = float(DataValue)
     1159
     1160         # Track values...
     1161         if DataLabel in OptionsInfo["HighlightValuesLabelsMap"]:
     1162             MiscUtil.PrintError("The data label, %s, specified in quratet \"%s,%s,%s,%s\", using \"--highlightValues\" option is not valid: Multiple occurences of data label" % (DataLabel, DataLabel, DataType, DataCriterion, DataValue))
     1163
     1164         OptionsInfo["HighlightValuesLabels"].append(DataLabel)
     1165         OptionsInfo["HighlightValuesLabelsMap"][DataLabel]  = DataLabel
     1166         OptionsInfo["HighlightValuesCanonicalLabelsMap"][DataLabel.lower()]  = DataLabel
     1167
     1168         OptionsInfo["HighlightValuesTypesMap"][DataLabel]  = DataType
     1169         OptionsInfo["HighlightValuesCriteriaMap"][DataLabel]  = DataCriterion
     1170         OptionsInfo["HighlightValuesCriteriaValuesMap"][DataLabel]  = DataValue
     1171
     1172 def ProcessHighlightValuesRangesOption():
     1173     """Process highlight values ranges option"""
     1174
     1175     OptionsInfo["HighlightRanges"]  = None
     1176     OptionsInfo["HighlightRangesLabels"]  = []
     1177
     1178     OptionsInfo["HighlightRangesLabelsMap"]  = {}
     1179     OptionsInfo["HighlightRangesCanonicalLabelsMap"]  = {}
     1180
     1181     OptionsInfo["HighlightRangesTypesMap"]  = {}
     1182     OptionsInfo["HighlightRangesCriteriaLowerMap"]  = {}
     1183     OptionsInfo["HighlightRangesCriteriaLowerValuesMap"]  = {}
     1184     OptionsInfo["HighlightRangesCriteriaUpperMap"]  = {}
     1185     OptionsInfo["HighlightRangesCriteriaUpperValuesMap"]  = {}
     1186
     1187     HighlightRanges = Options["--highlightValuesRanges"].strip()
     1188     if re.match("^None$", HighlightRanges, re.I):
     1189         return
     1190
     1191     OptionsInfo["HighlightRanges"]  = HighlightRanges
     1192     HighlightRangesWords = HighlightRanges.split(",")
     1193
     1194     if len(HighlightRangesWords) % 6:
     1195         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified in sextet \"%s\" using \"--highlightValuesRanges\" option must be a multiple of 6." % (len(HighlightRangesWords), HighlightRanges))
     1196
     1197     for Index in range(0, len(HighlightRangesWords), 6):
     1198         DataLabel = HighlightRangesWords[Index].strip()
     1199         DataType = HighlightRangesWords[Index + 1].strip()
     1200         LowerBoundDataCriterion = HighlightRangesWords[Index + 2].strip()
     1201         LowerBoundDataValue = HighlightRangesWords[Index + 3].strip()
     1202         UpperBoundDataCriterion = HighlightRangesWords[Index + 4].strip()
     1203         UpperBoundDataValue = HighlightRangesWords[Index + 5].strip()
     1204
     1205         SpecifiedSextet = "%s,%s,%s,%s,%s,%s" % (DataLabel, DataType, LowerBoundDataCriterion, LowerBoundDataValue, UpperBoundDataCriterion, UpperBoundDataValue)
     1206
     1207         CanonicalDataLabel = DataLabel.lower()
     1208         if CanonicalDataLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
     1209             MiscUtil.PrintError("The data label specified, %s, using option \"--highlightRanges\" has already been used in \"--highlightValues\" option" % DataLabel)
     1210
     1211         if not re.match("^(numeric|text)$", DataType, re.I):
     1212             MiscUtil.PrintError("The data type, %s, specified in sextet \"%s\" using \"--highlightValuesRanges\" option is not valid. Supported values: numeric text." % (DataType, SpecifiedSextet))
     1213
     1214         if not re.match("^(lt|le)$", LowerBoundDataCriterion, re.I):
     1215             MiscUtil.PrintError("The lower bound criterion, %s, specified in sextet \"%s\" using \"--highlightValuesRanges\" option is not valid. Supported values: lt or le." % (LowerBoundDataCriterion, SpecifiedSextet))
     1216
     1217         if not re.match("^(gt|ge)$", UpperBoundDataCriterion, re.I):
     1218             MiscUtil.PrintError("The upper bound criterion, %s, specified in sextet \"%s\" using \"--highlightValuesRanges\" option is not valid. Supported values: gt or ge." % (UpperBoundDataCriterion, SpecifiedSextet))
     1219
     1220         if re.match("^numeric$", DataType, re.I):
     1221             if not MiscUtil.IsNumber(LowerBoundDataValue):
     1222                 MiscUtil.PrintError("The lower bound data value, %s, specified in sextet \"%s\", using \"--highlightValuesRanges\" option is not valid. It must be a number for \"%s\" data type." % (LowerBoundDataValue, SpecifiedSextet, DataType))
     1223
     1224             if not MiscUtil.IsNumber(UpperBoundDataValue):
     1225                 MiscUtil.PrintError("The upper bound data value, %s, specified in sextet \"%s\", using \"--highlightValuesRanges\" option is not valid. It must be a number for \"%s\"data type." % (UpperBoundDataValue, SpecifiedSextet, DataType))
     1226
     1227             if float(LowerBoundDataValue) >= float(UpperBoundDataValue):
     1228                 MiscUtil.PrintError("The lower bound data value, %s, must be less than upper bound value, %s, specified in sextet \"%s\" using \"--highlightValuesRanges\" option." % (LowerBoundDataValue, UpperBoundDataValue,  SpecifiedSextet))
     1229
     1230             LowerBoundDataValue = float(LowerBoundDataValue)
     1231             UpperBoundDataValue = float(UpperBoundDataValue)
     1232         else:
     1233             if LowerBoundDataValue >= UpperBoundDataValue:
     1234                 MiscUtil.PrintError("The lower bound data value, %s, must be less than upper bound value, %s, specified in sextet \"%s\", using \"--highlightValuesRanges\" option is not valid. It must be a number for data type, %s" % (LowerBoundDataValue, UpperBoundDataValue,  SpecifiedSextet, DataType))
     1235
     1236         # Track values...
     1237         if DataLabel in OptionsInfo["HighlightRangesLabelsMap"]:
     1238             MiscUtil.PrintError("The data label, %s, specified in sextet \"%s\", using \"--highlightValuesRanges\" option is not valid. Multiple occurences of data label" % (DataLabel, SpecifiedSextet))
     1239
     1240         OptionsInfo["HighlightRangesLabels"].append(DataLabel)
     1241         OptionsInfo["HighlightRangesLabelsMap"][DataLabel]  = DataLabel
     1242         OptionsInfo["HighlightRangesCanonicalLabelsMap"][CanonicalDataLabel]  = DataLabel
     1243
     1244         OptionsInfo["HighlightRangesTypesMap"][DataLabel]  = DataType
     1245
     1246         OptionsInfo["HighlightRangesCriteriaLowerMap"][DataLabel]  = LowerBoundDataCriterion
     1247         OptionsInfo["HighlightRangesCriteriaLowerValuesMap"][DataLabel]  = LowerBoundDataValue
     1248         OptionsInfo["HighlightRangesCriteriaUpperMap"][DataLabel]  = UpperBoundDataCriterion
     1249         OptionsInfo["HighlightRangesCriteriaUpperValuesMap"][DataLabel]  = UpperBoundDataValue
     1250
     1251 def ProcessHighlightValuesClassesOption():
     1252     """Process highlight values classes option"""
     1253
     1254     OptionsInfo["HighlightClasses"]  = None
     1255     OptionsInfo["HighlightClassesRules"]  = None
     1256     OptionsInfo["HighlightClassesSynonymsMap"]  = None
     1257     OptionsInfo["HighlightClassesRandom"]  = False
     1258
     1259     OptionsInfo["HighlightClassesLabels"]  = []
     1260     OptionsInfo["HighlightClassesLabelsMap"]  = {}
     1261     OptionsInfo["HighlightClassesCanonicalLabelsMap"]  = {}
     1262
     1263     OptionsInfo["HighlightClassesTypesMap"]  = {}
     1264     OptionsInfo["HighlightClassesCriteriaMap"]  = {}
     1265     OptionsInfo["HighlightClassesCriteriaValuesMap"]  = {}
     1266
     1267     HighlightClasses = Options["--highlightValuesClasses"].strip()
     1268     if re.match("^None$", HighlightClasses, re.I):
     1269         return
     1270
     1271     OptionsInfo["HighlightClasses"]  = HighlightClasses
     1272
     1273     if re.match("^RuleOf5$", HighlightClasses, re.I):
     1274         HighlightClassessRules = "MolecularWeight,numeric,le,500,HydrogenBondDonors,numeric,le,5,HydrogenBondAcceptors,numeric,le,10,LogP,numeric,le,5"
     1275     elif re.match("^RuleOf3$", HighlightClasses, re.I):
     1276         HighlightClassessRules = "MolecularWeight,numeric,le,300,HydrogenBondDonors,numeric,le,3,HydrogenBondAcceptors,numeric,le,3,LogP,numeric,le,3,RotatableBonds,numeric,le,3,TPSA,numeric,le,60"
     1277     elif re.match("^DrugLike$", HighlightClasses, re.I):
     1278         HighlightClassessRules = "MolecularWeight,numeric,le,500,HydrogenBondDonors,numeric,le,5,HydrogenBondAcceptors,numeric,le,10,LogP,numeric,le,5,RotatableBonds,numeric,le,10,TPSA,numeric,le,140"
     1279     elif re.match("^Random$", HighlightClasses, re.I):
     1280         if OptionsInfo["HighlightValues"] is not None:
     1281             MiscUtil.PrintError("The value specified, %s, using option \"--highlightValuesClasses\" is not allowed in conjunction with \"--highlightValues\" option." % HighlightClasses)
     1282         if OptionsInfo["HighlightRanges"] is not None:
     1283             MiscUtil.PrintError("The value specified, %s, using option \"--highlightValuesClasses\" is not allowed in conjunction with \"--highlightRanges\" option ." % HighlightClasses)
     1284
     1285         OptionsInfo["HighlightClassesRandom"]  = True
     1286         return
     1287     else:
     1288         MiscUtil.PrintError("The value specified, %d, using option \"--highlightValuesClasses\" is not supported." % HighlightClasses)
     1289         return
     1290
     1291     OptionsInfo["HighlightClassesRules"]  = HighlightClassessRules
     1292
     1293     # Process rules for highlighting values...
     1294     HighlightClassesWords = HighlightClassessRules.split(",")
     1295     for Index in range(0, len(HighlightClassesWords), 4):
     1296         DataLabel = HighlightClassesWords[Index].strip()
     1297         DataType = HighlightClassesWords[Index + 1].strip()
     1298         DataCriterion = HighlightClassesWords[Index + 2].strip()
     1299         DataValue = HighlightClassesWords[Index + 3].strip()
     1300
     1301         DataValue = float(DataValue)
     1302
     1303         if DataLabel in OptionsInfo["HighlightClassesLabelsMap"]:
     1304             MiscUtil.PrintWarning("Ignoring duplicate datalabel, %s, specified in highlighting values rule for class, %s, in \"--highlightClassesValue\" option..." % (DataLabel, HighlightClasses))
     1305             continue
     1306
     1307         OptionsInfo["HighlightClassesLabels"].append(DataLabel)
     1308         OptionsInfo["HighlightClassesLabelsMap"][DataLabel] = DataLabel
     1309
     1310         OptionsInfo["HighlightClassesTypesMap"][DataLabel] = DataType
     1311         OptionsInfo["HighlightClassesCriteriaMap"][DataLabel] = DataCriterion
     1312         OptionsInfo["HighlightClassesCriteriaValuesMap"][DataLabel] = DataValue
     1313
     1314     # Set up synonyms for data labels corresponding to physicochemical properties
     1315     # calculated by MayaChemTools and RDKit...
     1316     OptionsInfo["HighlightClassesSynonymsMap"]  = {}
     1317     OptionsInfo["HighlightClassesSynonymsMap"]["MolecularWeight"]  = ["MolecularWeight", "MolWt"]
     1318     OptionsInfo["HighlightClassesSynonymsMap"]["HydrogenBondDonors"]  = ["HydrogenBondDonors", "NHOHCount"]
     1319     OptionsInfo["HighlightClassesSynonymsMap"]["HydrogenBondAcceptors"]  = ["HydrogenBondAcceptors", "NOCount"]
     1320     OptionsInfo["HighlightClassesSynonymsMap"]["LogP"]  = ["SLogP", "MolLogP"]
     1321     OptionsInfo["HighlightClassesSynonymsMap"]["RotatableBonds"]  = ["RotatableBonds", "NumRotatableBonds"]
     1322     OptionsInfo["HighlightClassesSynonymsMap"]["TPSA"]  = ["TPSA", "TPSA"]
     1323
     1324 def ProcessHighlightColorsOption():
     1325     """Process highlight colors option"""
     1326
     1327     OptionsInfo["HighlightColors"] = None
     1328     OptionsInfo["HighlightColorsType"] = None
     1329     OptionsInfo["HighlightColorsList"] = None
     1330
     1331     HighlightColors = "colorclass,table-success, table-danger"
     1332     if not re.match("^auto$", Options["--highlightColors"], re.I):
     1333         HighlightColors = Options["--highlightColors"].strip()
     1334         if MiscUtil.IsEmpty(HighlightColors):
     1335             MiscUtil.PrintError("The value specified using \"--highlightColors\" is empty.")
     1336
     1337     OptionsInfo["HighlightColors"] = re.sub(" ", "", HighlightColors)
     1338     HighlightColorsList = [Color.lower() for Color in OptionsInfo["HighlightColors"].split(",")]
     1339
     1340     if len(HighlightColorsList) != 3:
     1341         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"--highlightColors\" option must be 3." % (len(HighlightColorsList)))
     1342
     1343     ColorsType, Color1, Color2 = HighlightColorsList
     1344     if not re.match("^(colorclass|colorspec)$", ColorsType, re.I):
     1345         MiscUtil.PrintError("The color type, %s, specified using \"--highlightColors\" option is not valid. Supported values: colorclass or colorspec." % ColorsType)
     1346
     1347     ColorsList = [Color1, Color2]
     1348     if re.match("^colorclass$", ColorsType, re.I):
     1349         CheckOptionTableClassColorValues("--highlightColors", ColorsList)
     1350
     1351     OptionsInfo["HighlightColorsList"] = ColorsList
     1352     OptionsInfo["HighlightColorsType"] = ColorsType
     1353
     1354 def ProcessHighlightColorsRangesOption():
     1355     """Process highlight colors ranges option"""
     1356
     1357     OptionsInfo["HighlightColorsRanges"] = None
     1358     OptionsInfo["HighlightColorsRangesType"] = None
     1359     OptionsInfo["HighlightColorsRangesList"] = None
     1360
     1361     HighlightColors = "colorclass,table-success, table-warning, table-danger"
     1362     if not re.match("^auto$", Options["--highlightColorsRanges"], re.I):
     1363         HighlightColors = Options["--highlightColorsRanges"].strip()
     1364         if MiscUtil.IsEmpty(HighlightColors):
     1365             MiscUtil.PrintError("The value specified using \"--highlightColorsRanges\" is empty.")
     1366
     1367     OptionsInfo["HighlightColorsRanges"] = re.sub(" ", "", HighlightColors)
     1368     HighlightColorsList = [Color.lower() for Color in OptionsInfo["HighlightColorsRanges"].split(",")]
     1369
     1370     if len(HighlightColorsList) != 4:
     1371         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"--highlightColorsRanges\" option must be 4." % (len(HighlightColorsList)))
     1372
     1373     ColorsType, Color1, Color2, Color3 = HighlightColorsList
     1374     if not re.match("^(colorclass|colorspec)$", ColorsType, re.I):
     1375         MiscUtil.PrintError("The color type, %s, specified using \"--highlightColorsRanges\" option is not valid. Supported values: colorclass or colorspec." % ColorsType)
     1376
     1377     ColorsList = [Color1, Color2, Color3]
     1378     if re.match("^colorclass$", ColorsType, re.I):
     1379         CheckOptionTableClassColorValues("--highlightColorsRanges", ColorsList)
     1380
     1381     OptionsInfo["HighlightColorsRangesList"] = ColorsList
     1382     OptionsInfo["HighlightColorsRangesType"] = ColorsType
     1383
     1384 def ProcessHighlightColorsRandomOption():
     1385     """Process highlight colors random option"""
     1386
     1387     OptionsInfo["HighlightColorsRandom"] = None
     1388     OptionsInfo["HighlightColorsRandomType"] = None
     1389     OptionsInfo["HighlightColorsRandomList"] = None
     1390
     1391     HighlightColors = "colorclass,table-primary,table-success,table-danger,table-info,table-warning,table-secondary"
     1392     if not re.match("^auto$", Options["--highlightColorsRandom"], re.I):
     1393         HighlightColors = Options["--highlightColorsRandom"].strip()
     1394         if MiscUtil.IsEmpty(HighlightColors):
     1395             MiscUtil.PrintError("The value specified using \"--highlightColorsRandom\" is empty.")
     1396
     1397     OptionsInfo["HighlightColorsRandom"] = re.sub(" ", "", HighlightColors)
     1398     HighlightColorsList = [Color.lower() for Color in OptionsInfo["HighlightColorsRandom"].split(",")]
     1399
     1400     if len(HighlightColorsList) <= 1:
     1401         MiscUtil.PrintError("The number of comma delimited paramater names and values, %d, specified using \"--highlightColorsRandom\" option must be > 1." % (len(HighlightColorsList)))
     1402
     1403     ColorsType = HighlightColorsList[0]
     1404     ColorsList = HighlightColorsList[1:]
     1405
     1406     if not re.match("^(colorclass|colorspec)$", ColorsType, re.I):
     1407         MiscUtil.PrintError("The color type, %s, specified using \"--highlightColorsRandim\" option is not valid. Supported values: colorclass or colorspec." % ColorsType)
     1408
     1409     if re.match("^colorclass$", ColorsType, re.I):
     1410         CheckOptionTableClassColorValues("--highlightColorsRandom", ColorsList)
     1411
     1412     OptionsInfo["HighlightColorsRandomList"] = ColorsList
     1413     OptionsInfo["HighlightColorsRandomType"] = ColorsType
     1414
     1415 def CheckOptionTableClassColorValues(OptionName, ColorsList):
     1416     """Check names of table color classes and issue a warning for unknown names."""
     1417
     1418     TableClassColors = ["thead-dark", "thead-light", "table-primary", "table-success", "table-danger", "table-info", "table-warning", "table-active", "table-secondary", "table-light", "table-dark", "bg-primary", "bg-success", "bg-danger",  "bg-info", "bg-warning", "bg-secondary", "bg-dark", "bg-light"]
     1419
     1420     for Color in ColorsList:
     1421         if not Color in TableClassColors:
     1422             MiscUtil.PrintWarning("The color class name, %s, specified using option \"%s\" appears to be a unknown name..." % (Color, OptionName))
     1423
     1424 def ProcessOptions():
     1425     """Process and validate command line arguments and options"""
     1426
     1427     MiscUtil.PrintInfo("Processing options...")
     1428
     1429     # Validate options...
     1430     ValidateOptions()
     1431
     1432     OptionsInfo["Infile"] = Options["--infile"]
     1433     OptionsInfo["Outfile"] = Options["--outfile"]
     1434     OptionsInfo["Overwrite"] = Options["--overwrite"]
     1435
     1436     # No need for any RDKit specific --outfileParams....
     1437     OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters("--infileParams", Options["--infileParams"], OptionsInfo["Infile"])
     1438
     1439     AlignmentSMARTSPattern = None
     1440     if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
     1441         AlignmentSMARTSPattern = Options["--alignmentSMARTS"]
     1442     OptionsInfo["AlignmentSMARTSPattern"]  = AlignmentSMARTSPattern
     1443
     1444     Compute2DCoords = True
     1445     if re.match("^no$", Options["--compute2DCoords"], re.I):
     1446         Compute2DCoords = False
     1447     OptionsInfo["Compute2DCoords"]  = Compute2DCoords
     1448
     1449     CounterCol = True
     1450     if re.match("^no$", Options["--counterCol"], re.I):
     1451         CounterCol = False
     1452     OptionsInfo["CounterCol"]  = CounterCol
     1453
     1454     ColVisibility = True
     1455     if re.match("^no$", Options["--colVisibility"], re.I):
     1456         ColVisibility = False
     1457     OptionsInfo["ColVisibility"]  = ColVisibility
     1458
     1459     OptionsInfo["ColVisibilityCtrlMax"]  = int(Options["--colVisibilityCtrlMax"])
     1460
     1461     Footer = None
     1462     if not re.match("^None$", Options["--footer"], re.I):
     1463         Footer = Options["--footer"]
     1464     OptionsInfo["Footer"]  = Footer
     1465
     1466     FooterClass = Options["--footerClass"].strip()
     1467     if MiscUtil.IsEmpty(FooterClass):
     1468         MiscUtil.PrintError("The value specified using option \"--footerClass\" is empty.")
     1469     OptionsInfo["FooterClass"]  = FooterClass
     1470
     1471     FreezeCols = True
     1472     if re.match("^no$", Options["--freezeCols"], re.I):
     1473         FreezeCols = False
     1474     OptionsInfo["FreezeCols"]  = FreezeCols
     1475
     1476     Header = None
     1477     if not re.match("^None$", Options["--header"], re.I):
     1478         Header = Options["--header"]
     1479     OptionsInfo["Header"]  = Header
     1480
     1481     HeaderStyle = Options["--headerStyle"].strip()
     1482     if MiscUtil.IsEmpty(HeaderStyle):
     1483         MiscUtil.PrintError("The value specified using option \"--headerStyle\" is empty.")
     1484     OptionsInfo["HeaderStyle"]  = HeaderStyle
     1485
     1486     ProcessHighlightSMARTSOption()
     1487     ProcessHighlightDataOptions()
     1488
     1489     OptionsInfo["KeysNavigation"] = True
     1490     if re.match("^no$", Options["--keysNavigation"], re.I):
     1491         OptionsInfo["KeysNavigation"] = False
     1492
     1493     SizeValues = Options["--molImageSize"].split(",")
     1494     OptionsInfo["MolImageWidth"] = int(SizeValues[0])
     1495     OptionsInfo["MolImageHeight"] = int(SizeValues[1])
     1496
     1497     OptionsInfo["Paging"] = True
     1498     if re.match("^no$", Options["--paging"], re.I):
     1499         OptionsInfo["Paging"] = False
     1500
     1501     PagingType = Options["--pagingType"]
     1502     if not re.match("^(numbers|simple|simple_numbers|full|full_numbers|simple_number)$", Options["--pagingType"], re.I):
     1503         MiscUtil.PrintWarning("The paging type name, %s, specified using option \"--pagingType\" appears to be a unknown type..." % (PagingType))
     1504     OptionsInfo["PagingType"] = PagingType.lower()
     1505
     1506     OptionsInfo["PageLength"] = int(Options["--pageLength"])
     1507
     1508     OptionsInfo["RegexSearch"] = True
     1509     if re.match("^no$", Options["--regexSearch"], re.I):
     1510         OptionsInfo["RegexSearch"] = False
     1511
     1512     OptionsInfo["ShowMolName"] = True
     1513     OptionsInfo["ShowMolNameDataLabel"] = "Name"
     1514     if re.match("^no$", Options["--showMolName"], re.I):
     1515         OptionsInfo["ShowMolName"] = False
     1516
     1517     OptionsInfo["ShowMolNameAuto"] = True if re.match("^auto$", Options["--showMolName"], re.I) else False
     1518
     1519     OptionsInfo["ScrollX"] = True
     1520     if re.match("^no$", Options["--scrollX"], re.I):
     1521         OptionsInfo["ScrollX"] = False
     1522
     1523     OptionsInfo["ScrollY"] = True
     1524     if re.match("^no$", Options["--scrollY"], re.I):
     1525         OptionsInfo["ScrollY"] = False
     1526
     1527     OptionsInfo["ScrollYSize"] = Options["--scrollYSize"]
     1528     if re.match("vh$", Options["--scrollYSize"], re.I):
     1529         ScrollYSize = int(re.sub("vh$", "", Options["--scrollYSize"]))
     1530         if ScrollYSize <= 0:
     1531             MiscUtil.PrintError("The value specified, %s, for option \"--scrollYSize\" is not valid. Supported value: > 0 followed by \"vh\"" % Options["--scrollYSize"])
     1532
     1533     TableStyle = None
     1534     if not re.match("^None$", Options["--tableStyle"], re.I):
     1535         if re.match("^All$", Options["--tableStyle"], re.I):
     1536             TableStyle = "table table-striped table-bordered table-hover table-dark"
     1537         else:
     1538             TableStyle = re.sub(" ", "", Options["--tableStyle"])
     1539             for Style in [Style for Style in TableStyle.split(",")]:
     1540                 if not re.match("^(table|table-striped|table-bordered|table-hover|table-dark|table-sm)$", Style, re.I):
     1541                     MiscUtil.PrintWarning("The table style name, %s, specified using option \"-t, --tableStyle\" appears to be a unknown style..." % (Style))
     1542             TableStyle = re.sub(",", " ", TableStyle.lower())
     1543     OptionsInfo["TableStyle"]  = TableStyle
     1544
     1545     TableHeaderStyle = None
     1546     if not re.match("^None$", Options["--tableHeaderStyle"], re.I):
     1547         TableHeaderStyle = Options["--tableHeaderStyle"]
     1548         TableHeaderStyle = TableHeaderStyle.lower()
     1549         CheckOptionTableClassColorValues("--tableHeaderStyle", [TableHeaderStyle])
     1550     OptionsInfo["TableHeaderStyle"]  = TableHeaderStyle
     1551
     1552     OptionsInfo["TableFooter"] = True
     1553     if re.match("^no$", Options["--tableFooter"], re.I):
     1554         OptionsInfo["TableFooter"] = False
     1555
     1556     OptionsInfo["WrapText"] = True
     1557     if re.match("^no$", Options["--wrapText"], re.I):
     1558         OptionsInfo["WrapText"] = False
     1559
     1560     OptionsInfo["WrapTextWidth"] = int(Options["--wrapTextWidth"])
     1561
     1562 def RetrieveOptions():
     1563     """Retrieve command line arguments and options"""
     1564
     1565     # Get options...
     1566     global Options
     1567     Options = docopt(_docoptUsage_)
     1568
     1569     # Set current working directory to the specified directory...
     1570     WorkingDir = Options["--workingdir"]
     1571     if WorkingDir:
     1572         os.chdir(WorkingDir)
     1573
     1574     # Handle examples option...
     1575     if "--examples" in Options and Options["--examples"]:
     1576         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
     1577         sys.exit(0)
     1578
     1579 def ValidateOptions():
     1580     """Validate option values"""
     1581
     1582     if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
     1583         PatternMol = Chem.MolFromSmarts(Options["--alignmentSMARTS"])
     1584         if PatternMol is None:
     1585             MiscUtil.PrintError("The value specified, %s, using option \"--alignmentSMARTS\" is not a valid SMARTS: Failed to create pattern molecule" % Options["--alignmentSMARTS"])
     1586
     1587     MiscUtil.ValidateOptionTextValue("-c, --compute2DCoords", Options["--compute2DCoords"], "yes no auto")
     1588
     1589     MiscUtil.ValidateOptionTextValue("--counterCol", Options["--counterCol"], "yes no")
     1590     MiscUtil.ValidateOptionTextValue("--colVisibility", Options["--colVisibility"], "yes no")
     1591     MiscUtil.ValidateOptionIntegerValue("--colVisibilityCtrlMax", Options["--colVisibilityCtrlMax"], {">": 0})
     1592
     1593     MiscUtil.ValidateOptionTextValue("--freezeCols", Options["--freezeCols"], "yes no")
     1594     MiscUtil.ValidateOptionTextValue("--highlightValuesClasses", Options["--highlightValuesClasses"], "RuleOf5 RuleOf3 DrugLike Random None")
     1595
     1596     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
     1597     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol smi csv tsv txt")
     1598
     1599     MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "html")
     1600     MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"])
     1601     MiscUtil.ValidateOptionsDistinctFileNames("-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"])
     1602
     1603     MiscUtil.ValidateOptionTextValue("-k, --keysNavigation", Options["--keysNavigation"], "yes no")
     1604
     1605     MiscUtil.ValidateOptionNumberValues("-m, --molImageSize", Options["--molImageSize"], 2, ",", "integer", {">": 0})
     1606
     1607     MiscUtil.ValidateOptionTextValue("-p, --paging", Options["--paging"], "yes no")
     1608     MiscUtil.ValidateOptionIntegerValue("--pageLength", Options["--pageLength"], {">": 0})
     1609     MiscUtil.ValidateOptionTextValue("-r, --regexSearch", Options["--regexSearch"], "yes no")
     1610
     1611     MiscUtil.ValidateOptionTextValue("--showMolName", Options["--showMolName"], "yes no auto")
     1612
     1613     MiscUtil.ValidateOptionTextValue("--scrollX", Options["--scrollX"], "yes no")
     1614     MiscUtil.ValidateOptionTextValue("--scrollY", Options["--scrollY"], "yes no")
     1615     if not re.search("vh$", Options["--scrollYSize"], re.I):
     1616         MiscUtil.ValidateOptionIntegerValue("--scrollYSize", Options["--scrollYSize"], {">": 0})
     1617
     1618     MiscUtil.ValidateOptionTextValue("--tableFooter", Options["--tableFooter"], "yes no")
     1619
     1620     MiscUtil.ValidateOptionTextValue("--wrapText", Options["--wrapText"], "yes no")
     1621     MiscUtil.ValidateOptionIntegerValue("--wrapTextWidth", Options["--wrapTextWidth"], {">": 0})
     1622
     1623 # Setup a usage string for docopt...
     1624 _docoptUsage_ = """
     1625 RDKitDrawMoleculesAndDataTable.py - Generate a HTML data table
     1626
     1627 Usage:
     1628     RDKitDrawMoleculesAndDataTable.py [--alignmentSMARTS <SMARTS>]
     1629                              [--compute2DCoords <yes or  no>] [--counterCol <yes or no>]
     1630                              [--colVisibility <yes or no>] [--colVisibilityCtrlMax <number>] [--footer <text>]
     1631                              [--footerClass <text>] [--freezeCols <yes or no>] [--header <text>]
     1632                              [--headerStyle <text>] [--highlightSMARTS <SMARTS,...>]
     1633                              [--highlightValues <datalabel,datatype,criterion,value,...>]
     1634                              [--highlightValuesRanges <datalabel,datatype,criterion1,vaue1,criterion2,value2...>]
     1635                              [--highlightValuesClasses <RuleOf5,RuleOf3,...>]
     1636                              [--highlightColors <colortype,color1,color2>]
     1637                              [--highlightColorsRanges <colortype,color1,color2,color3>]
     1638                              [--highlightColorsRandom <colottype,color1,color2,...>]
     1639                              [--infileParams <Name,Value,...>] [--keysNavigation <yes or no>]
     1640                              [--molImageSize <width,height>] [--overwrite] [--paging <yes or no>]
     1641                              [--pagingType <numbers,simple, ...>] [--pageLength <number>]
     1642                              [--regexSearch <yes or no>] [--showMolName <yes or no>]
     1643                              [--scrollX <yes or no>] [--scrollY <yes or no>] [--scrollYSize <number>]
     1644                              [--tableStyle <table,table-striped,...>] [--tableFooter <yes or no>]
     1645                              [--tableHeaderStyle <thead-dark,thead-light,...>] [--wrapText <yes or no>]
     1646                              [--wrapTextWidth <number>] [-w <dir>] -i <infile> -o <outfile>
     1647     RDKitDrawMoleculesAndDataTable.py -h | --help | -e | --examples
     1648
     1649 Description:
     1650     Generate an interactive HTML table with columns corresponding to molecules
     1651     and available alphanumerical data in an input file. The drawing of molecules are
     1652     embedded in the columns as in line SVG images.
     1653
     1654     The interactive HTML table may contain multiple columns with drawing of
     1655     molecules. These columns are automatically generated for each data field in SD
     1656     file or a column name in SMILES and CSV/TSV file containing SMILES
     1657     string in their names. The first molecular drawing column in the HTML table
     1658     represents primary molecular structure data available in an input file. It
     1659     corresponds to MOL block is SD file or a first column containing SMILES string
     1660     in its name in SMILES and CSV/TSV files.
     1661
     1662     The interactive table requires internet access for viewing in a browser and
     1663     employs the following frameworks: JQuery, Bootstrap, and DataTable. It provides
     1664     the following functionality: sorting by columns, page length control, page
     1665     navigation, searching data with regular expressions, and horizontal/vertical
     1666     scrolling, row highlighting during hovering, a counter column, freezing of primary
     1667     structure and counter columns, and column visibility control.
     1668
     1669     The supported input file formats are: Mol (.mol), SD (.sdf, .sd), SMILES (.smi),
     1670     CSV/TSV (.csv, .tsv, .txt)
     1671
     1672     The supported output file format is HTML (.html).
     1673
     1674 Options:
     1675     -a, --alignmentSMARTS <SMARTS>  [default: none]
     1676         SMARTS pattern for aligning molecules to a common template. This option is
     1677         only used for primary molecular data in SD, SMILES and CSV/TSV files. It is
     1678         ignored for all other molecular coordinates corresponding to data fields in SD
     1679         file or columns in SMILES and CSV/TSV files containing SMILES string in their
     1680         names.
     1681     -c, --compute2DCoords <yes or no>  [default: yes]
     1682         Compute 2D coordinates of molecules before drawing. Default: yes for SMILES
     1683         strings in SMILES, CSV/TSV, and SD file data fields. In addition, 2D coordinated are
     1684         always calculated for molecules corresponding to data fields in SD file or columns
     1685         in SMILES and CSV/TSV files containing SMILES string in their names.
     1686     --counterCol <yes or no>  [default: yes]
     1687         Show a counter column as the first column in the table. It contains the position
     1688         for each row in the table.
     1689     --colVisibility <yes or no>  [default: yes]
     1690         Show a dropdown button to toggle visibility of columns in the table. The counter
     1691         and primary structure columns are excluded from the list.
     1692     --colVisibilityCtrlMax <number>  [default: 25]
     1693         Maximum number of columns to show in column visibility dropdown button. The
     1694         rest of the data columns are not listed in the dropdown and are shown in the table.
     1695         A word to the wise: The display of too many columns appear to hang interactive
     1696         Javascript framework for Bootstrap and DataTables.
     1697     --freezeCols <yes or no>  [default: yes]
     1698         Lock counter and primary structure columns in place during horizontal scrolling.
     1699     --footer <text>  [default: none]
     1700         Footer text to insert at the bottom of the HTML page after the table.
     1701     --footerClass <text>  [default: small text-center text-muted]
     1702         Footer class style to use with <p> tag.
     1703     -e, --examples
     1704         Print examples.
     1705     -h, --help
     1706         Print this help message.
     1707     --header <text>  [default: none]
     1708         Header text to insert at the top of the HTML page before the table.
     1709     --headerStyle <text>  [default: h5]
     1710         Header style to use. Possible values: h1 to h6.
     1711     --highlightSMARTS <SMARTS,...>  [default: none]
     1712         SMARTS pattern for highlighting atoms and bonds in molecules. All matched
     1713         substructures are highlighted.
     1714
     1715         The SMARTS string is used to highlight atoms and bonds in drawing of
     1716         molecules present in a HTML table across multiple columns. These columns
     1717         correspond to data field labels in SD file or a column name in SMILES and
     1718         CSV/TSV file containing SMILES string in their names. The first molecular
     1719         drawing column in HTML table corresponds to primary molecular structure
     1720         data available in an input file. It is identified by a label 'Structure' across
     1721         all input formats.
     1722
     1723         A single SMARTS string is used to highlight a common substructure across
     1724         all columns containing drawing of molecules in HTML table.
     1725
     1726         Format:
     1727
     1728             SMARTS
     1729             Structure,SMARTS1,DataLabel,SMARTS2,...
     1730             Structure,SMARTS1,Collabel,SMARTS2,...
     1731
     1732         Example:
     1733
     1734             c1ccccc1
     1735             Structure,c1ccccc1,SMILESR1,c1ccccc1,SMILESR2,c1ccccc1
     1736
     1737     --highlightValues <datalabel,datatype,criterion,value,...>  [default: none]
     1738         Highlighting methodology to use for highlighting  alphanumerical data
     1739         corresponding to data fields in SD file or column names in SMILES and
     1740         CSV/TSV text files.
     1741
     1742         Input text contains these quartets: DataLabel, DataType, Criterion, Value.
     1743         Possible datatype values: numeric, text. Possible criterion values for numeric
     1744         and text: gt, lt, ge, le.
     1745
     1746         The 'datalabel' corresponds to either data field label in SD file or column name
     1747         in SMILES and CSV/TSV text files.
     1748
     1749         Examples:
     1750
     1751             MolecularWeight,numeric,le,500
     1752             MolecularWeight,numeric,le,450,SLogP,numeric,le,5
     1753             Name,text,eq,Aspirin
     1754             Name,regex,eq,acid|amine
     1755
     1756     --highlightValuesRanges <datalabel,datatype,...>  [default: none]
     1757         Highlighting methodology to use for highlighting ranges of alphanumerical
     1758         data corresponding to data fields in SD file or column names in SMILES and
     1759         CSV/TSV text files.
     1760
     1761         Input text contains these sextets: DataLabel, DataType, CriterionLowerBound,
     1762         LowerBoundValue, CriterionUpperBound, UpperBoundValue.
     1763
     1764         Possible datatype values: numeric or text. Possible criterion values: Lower
     1765         bound value - lt, le; Upper bound value: gt, ge.
     1766
     1767         The 'datalabel' corresponds to either data field label in SD file or column name
     1768         in SMILES and CSV/TSV text files.
     1769
     1770         Examples:
     1771
     1772             MolecularWeight,numeric,lt,450,gt,1000
     1773             MolecularWeight,numeric,lt,450,gt,1000,SLogP,numeric,lt,0,gt,5
     1774
     1775     --highlightValuesClasses <RuleOf5,RuleOf3,...>  [default: none]
     1776         Highlighting methodology to use for highlighting ranges of numerical data
     1777         data corresponding to specific set of data fields in SD file or column names in
     1778         SMILES and CSV/TSV text files. Possible values: RuleOf5, RuleOf3, DrugLike,
     1779         Random.
     1780
     1781         The following value classes are supported: RuleOf5, RuleOf3, LeadLike, DrugLike.
     1782         LeadLike is equivalent to RuleOf3.
     1783
     1784         Each supported class encompasses a specific set of data labels along with
     1785         appropriate criteria to compare and highlight column values, except for
     1786         'Random' class. The data labels in these classes are automatically associated
     1787         with appropriate data fields in SD file or column names in SMILES and CSV/TSV
     1788         text files.
     1789
     1790         No data labels are associated with 'Random' class. It is used to highlight
     1791         available alphanumeric data by randomly selecting a highlight color from the
     1792         list of colors specified using '--highlightColorsRandom' option. The 'Random'
     1793         class value is not allowed in conjunction with '--highlightValues' or
     1794         '--highlightValuesRanges'.
     1795
     1796         The rules to highlight values for the supported classes are as follows.
     1797
     1798         RuleOf5 [ Ref 91 ]:
     1799
     1800             MolecularWeight,numeric,le,500 (MolecularWeight <= 500)
     1801             HydrogenBondDonors,numeric,le,5 (HydrogenBondDonors <= 5)
     1802             HydrogenBondAcceptors,numeric,le,10 (HydrogenBondAcceptors <= 10)
     1803             LogP,numeric,le,5 (LogP <= 5)
     1804
     1805         RuleOf3 or LeadLike [ Ref 92 ]:
     1806
     1807             MolecularWeight,numeric,le,300 (MolecularWeight <= 300)
     1808             HydrogenBondDonors,numeric,le,3 (HydrogenBondDonors <= 3)
     1809             HydrogenBondAcceptors,numeric,le,3 (HydrogenBondAcceptors <= 3)
     1810             LogP,numeric,le,3 (LogP <= 3)
     1811             RotatableBonds,numeric,le,3 (RotatableBonds <= 3)
     1812             TPSA,numeric,le,60 (TPSA <= 60)
     1813
     1814         DrugLike:
     1815
     1816             MolecularWeight,numeric,le,500 (MolecularWeight <= 500)
     1817             HydrogenBondDonors,numeric,le,5 (HydrogenBondDonors <= 5)
     1818             HydrogenBondAcceptors,numeric,le,10 (HydrogenBondAcceptors <= 10)
     1819             LogP,numeric,le,5 (LogP <= 5)
     1820             RotatableBonds,numeric,le,10 (RotatableBonds <= 10)
     1821             TPSA,numeric,le,140 (TPSA <= 140)
     1822
     1823         The following synonyms are automatically detected for data labels used
     1824         by MayaChemTools and RDKit packages during the calculation of
     1825         physicochemical properties.
     1826
     1827         MayaChemTools: MolecularWeight, HydrogenBondDonors, HydrogenBondAcceptors,
     1828         SLogP, RotatableBonds, TPSA.
     1829
     1830         RDKit: MolWt,  NHOHCount, NOCount, MolLogP, NumRotatableBonds, TPSA
     1831
     1832     --highlightColors <colortype,color1,color2>  [default: auto]
     1833         Background colors used to highlight column values based on criterion
     1834         specified by '--highlightValues' and '--highlightColorsClasses' option. Default
     1835         value: colorclass,table-success, table-danger.
     1836
     1837         The first color is used to highlight column values that satisfy the specified
     1838         criterion for the column. The second color highlights the rest of the values
     1839         in the column.
     1840
     1841         Possible values for colortype: colorclass or colorspec.
     1842
     1843         Any valid bootstrap contextual color class is supported for 'colorclass'
     1844         color type. For example: table-primary (Blue), table-success (Green),
     1845         table-danger (Red), table-info (Light blue), table-warning (Orange),
     1846         table-secondary (Grey), table-light (Light grey), and  table-dark (Dark grey).
     1847
     1848         The following bootstrap color classes may also used: bg-primary bg-success,
     1849         bg-danger bg-info, bg-warning, bg-secondary.
     1850
     1851         Any valid color name or hexadecimal color specification is supported for
     1852         'colorspec' color type: For example: red, green, blue, #ff000, #00ff00, #0000ff.
     1853     --highlightColorsRanges <colortype,color1,color2,color3>  [default: auto]
     1854         Background colors used to highlight column values using criteria specified
     1855         by '--highlightValuesRanges' option. Default value:  colorclass, table-success,
     1856         table-warning, table-danger.
     1857
     1858         The first and third color are used to highlight column values lower and higher
     1859         than the specified values for the lower and upper bound. The middle color highlights
     1860         the rest of the values in the column.
     1861
     1862         The supported color type and values are explained in the section for '--highlightColors'.
     1863     --highlightColorsRandom <colortype,color1,color2,...>  [default: auto]
     1864         Background color list to use for randomly selecting a color  to highlight
     1865         column values during 'Random" value of '--highlightValuesClasses' option.
     1866
     1867         Default value:  colorclass,table-primary,table-success,table-danger,table-info,
     1868         table-warning,table-secondary.
     1869
     1870         The supported color type and values are explained in the section for '--highlightColors'.
     1871     -i, --infile <infile>
     1872         Input file name.
     1873     --infileParams <Name,Value,...>  [default: auto]
     1874         A comma delimited list of parameter name and value pairs for reading
     1875         molecules from files. The supported parameter names for different file
     1876         formats, along with their default values, are shown below:
     1877
     1878             SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
     1879             SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
     1880                 sanitize,yes
     1881
     1882         Possible values for smilesDelimiter: space, comma or tab.
     1883     -k, --keysNavigation <yes or no>  [default: yes]
     1884         Provide Excel like keyboard cell navigation for the table.
     1885     -m, --molImageSize <width,height>  [default: 200,150]
     1886         Image size of a molecule in pixels.
     1887     -o, --outfile <outfile>
     1888         Output file name.
     1889     --overwrite
     1890         Overwrite existing files.
     1891     -p, --paging <yes or no>  [default: yes]
     1892         Provide page navigation for browsing data in the table.
     1893     --pagingType <numbers, simple, ...>  [default: full_numbers]
     1894         Type of page navigation. Possible values: numbers, simple, simple_numbers,
     1895         full, full_numbers, or first_last_numbers.
     1896
     1897             numbers - Page number buttons only
     1898             simple - 'Previous' and 'Next' buttons only
     1899             simple_numbers - 'Previous' and 'Next' buttons, plus page numbers
     1900             full - 'First', 'Previous', 'Next' and 'Last' buttons
     1901             full_numbers - 'First', 'Previous', 'Next' and 'Last' buttons, plus
     1902                 page numbers
     1903             first_last_numbers - 'First' and 'Last' buttons, plus page numbers
     1904
     1905     --pageLength <number>  [default: 15]
     1906         Number of rows to show per page.
     1907     -r, --regexSearch <yes or no>  [default: yes]
     1908         Allow regular expression search through alphanumerical data in the table.
     1909     -s, --showMolName <yes or no>  [default: auto]
     1910         Show molecule names in a column next to the column corresponding to primary
     1911         structure data in SD and SMILES file. The default value is yes for SD and SMILES file.
     1912         This option is ignored for CSV/TSV text files.
     1913     --scrollX <yes or no>  [default: yes]
     1914         Provide horizontal scroll bar in the table as needed.
     1915     --scrollY <yes or no>  [default: yes]
     1916         Provide vertical scroll bar in the table as needed.
     1917     --scrollYSize <number>  [default: 75vh]
     1918         Maximum height of table viewport either in pixels or percentage of the browser
     1919         window height before providing a vertical scroll bar. Default: 75% of the height of
     1920         browser window.
     1921     -t, --tableStyle <table,table-striped,...>  [default: table,table-hover,table-sm]
     1922         Style of table. Possible values: table, table-striped, table-bordered,
     1923         table-hover, table-dark, table-sm, none, or All. Default: 'table,table-hover'. A
     1924         comma delimited list of any valid Bootstrap table styles is also supported.
     1925     --tableFooter <yes or no>  [default: yes]
     1926         Show column headers at the end of the table.
     1927     --tableHeaderStyle <thead-dark,thead-light,...>  [default: thead-dark]
     1928         Style of table header. Possible values: thead-dark, thead-light, or none.
     1929         The names of the following contextual color classes are also supported:
     1930         table-primary (Blue), table-success (Green), table-danger (Red), table-info
     1931         (Light blue), table-warning (Orange), table-active (Grey), table-light (Light
     1932         grey), and  table-dark (Dark grey).
     1933     -w, --workingdir <dir>
     1934         Location of working directory which defaults to the current directory.
     1935     --wrapText <yes or no>  [default: yes]
     1936         Wrap alphanumeric text using <br/> delimiter for display in a HTML table.
     1937     --wrapTextWidth <number>  [default: 40]
     1938         Maximum width in characters before wraping alphanumeric text for display
     1939         in a HTML table.
     1940
     1941 Examples:
     1942     To generate a HTML table containing structure and alphanumeric data for
     1943     molecules in a SD file along with all the bells and whistles to interact with
     1944     the table, type:
     1945
     1946         % RDKitDrawMoleculesAndDataTable.py -i Sample.sdf -o SampleOut.html
     1947
     1948     To generate a HTML table containing structure and alphanumeric data for
     1949     molecules in a SMILES file along with all the bells and whistles to interact
     1950     with the table, type:
     1951
     1952         % RDKitDrawMoleculesAndDataTable.py  -i Sample.smi -o SampleOut.html
     1953
     1954     To generate a HTML table containing multiple structure columns for molecules
     1955     in a CSV file along with all the bells and whistles to interact with the table, type:
     1956
     1957         % RDKitDrawMoleculesAndDataTable.py -i SampleSeriesRGroupsD3R.csv
     1958           -o SampleSeriesRGroupsD3ROut.html
     1959
     1960     To generate a HTML table containing structure and alphanumeric data for
     1961     molecules in a SD file along without any bells and whistles to interact with
     1962     the table, type:
     1963
     1964         % RDKitDrawMoleculesAndDataTable.py --colVisibility no --freezeCols no
     1965           --keysNavigation no --paging no --regexSearch no --scrollX no
     1966           --scrollY no -i Sample.sdf -o SampleOut.html
     1967
     1968     To generate a HTML table containing structure and alphanumeric data for
     1969     molecules in a SD file along with highlighting molecular weight values
     1970     using a specified criterion, type:
     1971
     1972         % RDKitDrawMoleculesAndDataTable.py  --highlightValues
     1973           "MolecularWeight,numeric,le,500" -i Sample.sdf -o SampleOut.html
     1974
     1975     To generate a HTML table containing structure and alphanumeric data for
     1976     molecules in a SD file along with highlighting range of molecular weight values
     1977     using a specified criterion, type:
     1978
     1979         % RDKitDrawMoleculesAndDataTable.py  --highlightValuesRanges
     1980           "MolecularWeight,numeric,lt,400,gt,500" -i Sample.sdf -o SampleOut.html
     1981
     1982     To generate a HTML table containing structure and alphanumeric data for
     1983     molecules in a SD file along with highlighting molecular weight values and
     1984     ranges of SLogP values using a specified criterion and color schemes, type:
     1985
     1986         % RDKitDrawMoleculesAndDataTable.py  --highlightValues
     1987           "MolecularWeight,numeric,le,500" --highlightValuesRanges
     1988           "SLogP,numeric,lt,0,gt,5" --highlightColors "colorclass,table-success,
     1989           table-danger" --highlightColorsRanges "colorclass,table-danger,
     1990           table-success,table-warning" -i Sample.sdf -o SampleOut.html
     1991
     1992     To generate a HTML table containing structure and alphanumeric data for
     1993     molecules in a SD file along with highlighting RuleOf5 physicochemical
     1994     properties using a pre-defined set of criteria, type:
     1995
     1996         % RDKitDrawMoleculesAndDataTable.py  --highlightValuesClasses RuleOf5
     1997           -i Sample.sdf -o SampleOut.html
     1998
     1999     To generate a HTML table containing structure and alphanumeric data for
     2000     molecules in a SD file along with all the bells and whistles to interact
     2001     with the table and highlight a specific SMARTS pattern in molecules, type:
     2002
     2003         % RDKitDrawMoleculesAndDataTable.py  --highlightSMARTS "c1ccccc1"
     2004           -i Sample.sdf -o SampleOut.html
     2005
     2006     To generate a HTML table containing structure and alphanumeric data for
     2007     molecules in a SD file along with highlighting of values using random colors
     2008     from a default list of colors, type:
     2009
     2010         % RDKitDrawMoleculesAndDataTable.py --highlightValuesClasses Random
     2011           -i Sample.sdf -o SampleOut.html
     2012
     2013     To generate a HTML table containing structure and alphanumeric data for
     2014     molecules in a SD file along with highlighting of values using random colors
     2015     from a specified list of colors, type:
     2016
     2017         % RDKitDrawMoleculesAndDataTable.py --highlightValuesClasses Random
     2018           --highlightColorsRandom "colorspec,Lavendar,MediumPurple,SkyBlue,
     2019           CornflowerBlue,LightGreen,MediumSeaGreen,Orange,Coral,Khaki,Gold,
     2020           Salmon,LightPink,Aquamarine,MediumTurquoise,LightGray"
     2021           -i Sample.sdf -o SampleOut.html
     2022
     2023     To generate a HTML table containing structure and alphanumeric data for
     2024     molecules in a SMILES file specific columns, type:
     2025
     2026         % RDKitDrawMoleculesAndDataTable.py --infileParams "smilesDelimiter,
     2027           comma, smilesColumn,1,smilesNameColumn,2"
     2028           -i SampleSMILES.csv -o SampleOut.html
     2029
     2030 Author:
     2031     Manish Sud(msud@san.rr.com)
     2032
     2033 See also:
     2034     RDKitConvertFileFormat.py, RDKitDrawMolecules.py, RDKitRemoveDuplicateMolecules.py,
     2035     RDKitSearchFunctionalGroups.py, RDKitSearchSMARTS.py
     2036
     2037 Copyright:
     2038     Copyright (C) 2018 Manish Sud. All rights reserved.
     2039
     2040     The functionality available in this script is implemented using RDKit, an
     2041     open source toolkit for cheminformatics developed by Greg Landrum.
     2042
     2043     This file is part of MayaChemTools.
     2044
     2045     MayaChemTools is free software; you can redistribute it and/or modify it under
     2046     the terms of the GNU Lesser General Public License as published by the Free
     2047     Software Foundation; either version 3 of the License, or (at your option) any
     2048     later version.
     2049
     2050 """
     2051
     2052 if __name__ == "__main__":
     2053     main()