MayaChemTools

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