def GetMolPopoverTag(Mol):
    """Set up a popover window containing any additional information about molecule."""

    if not OptionsInfo["Popover"]:
        return None

    # Set up data label and values...
    AvailableDataLabels = Mol.GetPropNames(includePrivate=False,
                                           includeComputed=False)

    DataContentLines = []
    MaxDataCharWidth = OptionsInfo["PopoverTextWidth"]
    MaxDataDisplayCount = OptionsInfo["PopoverDataCount"]

    DataDisplayCount = 0
    SkippedDataDisplay = False
    for DataLabel in AvailableDataLabels:
        DataDisplayCount += 1
        if DataDisplayCount > MaxDataDisplayCount:
            SkippedDataDisplay = True
            break

        DataValue = "%s" % Mol.GetProp(DataLabel)
        DataValue = DataValue.strip()
        if MiscUtil.IsEmpty(DataValue):
            continue

        # Change any new lines to ;
        if re.search("(\r\n|\r|\n)", DataValue):
            DataValue = re.sub("(\r\n|\r|\n)", "; ", DataValue)

        DataValue = MiscUtil.TruncateText(DataValue, MaxDataCharWidth, "...")
        DataValue = MiscUtil.ReplaceHTMLEntitiesInText(DataValue)

        DataContent = "<b>%s</b>: %s" % (DataLabel, DataValue)
        DataContentLines.append(DataContent)

    if not len(DataContentLines):
        return None

    if SkippedDataDisplay:
        DataContent = "<b>... ... ...</b>"
        DataContentLines.append(DataContent)

        DataContent = "Showing 1 to %s of %s" % (MaxDataDisplayCount,
                                                 len(AvailableDataLabels))
        DataContentLines.append(DataContent)
    else:
        DataContent = "Showing 1 to %s of %s" % (DataDisplayCount,
                                                 len(AvailableDataLabels))
        DataContentLines.append(DataContent)

    DataContent = MiscUtil.JoinWords(DataContentLines, "<br/>")
    PopoverTag = """class="MolPopover" data-toggle="popover" data-html="true" data-trigger="click" data-placement="right" title="<span class='small'><b>Additional Information</b></span>" data-content="<span class='small'>%s</span>" """ % DataContent

    return PopoverTag
def WriteRGroups(Mols, RGroups, UnmatchedMolIndices):
    """Write out R groups"""

    Outfile = OptionsInfo["Outfile"]
    UnmatchedOutfile = OptionsInfo["UnmatchedOutfile"]
    RemoveUnmatchedMode = OptionsInfo["RemoveUnmatchedMode"]

    Compute2DCoords = OptionsInfo["OutfileParams"]["Compute2DCoords"]

    TextOutFileMode = OptionsInfo["TextOutFileMode"]
    TextOutFileDelim = OptionsInfo["TextOutFileDelim"]
    Quote = OptionsInfo["TextOutQuote"]

    SMILESIsomeric = OptionsInfo["OutfileParams"]["SMILESIsomeric"]
    SMILESKekulize = OptionsInfo["OutfileParams"]["Kekulize"]

    # Setup writers...
    Writer = None
    UnmatchedWriter = None
    if TextOutFileMode:
        Writer = open(Outfile, "w")
        if RemoveUnmatchedMode:
            UnmatchedWriter = open(UnmatchedOutfile, "w")
    else:
        Writer = RDKitUtil.MoleculesWriter(Outfile,
                                           **OptionsInfo["OutfileParams"])
        if RemoveUnmatchedMode:
            UnmatchedWriter = RDKitUtil.MoleculesWriter(
                UnmatchedOutfile, **OptionsInfo["OutfileParams"])

    if Writer is None:
        MiscUtil.PrintError("Failed to setup a writer for output fie %s " %
                            Outfile)
    if RemoveUnmatchedMode:
        if UnmatchedWriter is None:
            MiscUtil.PrintError("Failed to setup a writer for output fie %s " %
                                UnmatchedOutfile)

    if RemoveUnmatchedMode:
        MiscUtil.PrintInfo("\nGenerating files: %s %s..." %
                           (Outfile, UnmatchedOutfile))
    else:
        MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile)

    # Set up data keys and labels for  core and R groups...
    RGroupsDataKeys = []
    RGroupsDataLabels = []
    CoreDataLabelPresent = False
    for DataLabel in sorted(RGroups):
        if re.match("^Core", DataLabel, re.I):
            CoreDataLabelPresent = True
            RGroupsDataKeys.append(DataLabel)
            RGroupsDataLabels.append("SMILES%s" % DataLabel)
        elif re.match("^R", DataLabel, re.I):
            RGroupsDataKeys.append(DataLabel)
            RGroupsDataLabels.append("SMILES%s" % DataLabel)
        else:
            MiscUtil.PrintWarning(
                "Ignoring unknown R group data label, %s, found during R group decomposition..."
                % DataLabel)

    if CoreDataLabelPresent:
        RGroupsCategoriesCount = len(RGroupsDataLabels) - 1
    else:
        RGroupsCategoriesCount = len(RGroupsDataLabels)
    RGroupsMolUnmatchedCount = len(UnmatchedMolIndices)
    RGroupsMolMatchedCount = len(Mols) - RGroupsMolUnmatchedCount

    # Wite out headers for a text file...
    if TextOutFileMode:
        LineWords = ["SMILES", "Name"]

        if RemoveUnmatchedMode:
            Line = MiscUtil.JoinWords(LineWords, TextOutFileDelim, Quote)
            UnmatchedWriter.write("%s\n" % Line)

        LineWords.extend(RGroupsDataLabels)
        Line = MiscUtil.JoinWords(LineWords, TextOutFileDelim, Quote)
        Writer.write("%s\n" % Line)

    MolCount = 0
    RGroupsResultIndex = -1

    for MolIndex, Mol in enumerate(Mols):
        MolCount += 1

        UnmatchedMol = False
        if MolIndex in UnmatchedMolIndices:
            UnmatchedMol = True

        if UnmatchedMol:
            RGroupsDataSMILES = [""] * len(RGroupsDataKeys)
        else:
            RGroupsResultIndex += 1
            RGroupsDataSMILES = [
                RGroups[RGroupsDataKey][RGroupsResultIndex]
                for RGroupsDataKey in RGroupsDataKeys
            ]

        if TextOutFileMode:
            # Write out text file including SMILES file...
            MolSMILES = Chem.MolToSmiles(Mol,
                                         isomericSmiles=SMILESIsomeric,
                                         kekuleSmiles=SMILESKekulize)
            MolName = RDKitUtil.GetMolName(Mol, MolCount)
            LineWords = [MolSMILES, MolName]

            if UnmatchedMol and RemoveUnmatchedMode:
                Line = MiscUtil.JoinWords(LineWords, TextOutFileDelim, Quote)
                UnmatchedWriter.write("%s\n" % Line)
            else:
                LineWords.extend(RGroupsDataSMILES)
                Line = MiscUtil.JoinWords(LineWords, TextOutFileDelim, Quote)
                Writer.write("%s\n" % Line)
        else:
            # Write out SD file...
            if Compute2DCoords:
                AllChem.Compute2DCoords(Mol)

            if UnmatchedMol and RemoveUnmatchedMode:
                UnmatchedWriter.write(Mol)
            else:
                for (Name, Value) in zip(RGroupsDataLabels, RGroupsDataSMILES):
                    Mol.SetProp(Name, Value)
                Writer.write(Mol)

    if Writer is not None:
        Writer.close()
    if UnmatchedWriter is not None:
        UnmatchedWriter.close()

    MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
    MiscUtil.PrintInfo("Number of  R group categories: %d" %
                       RGroupsCategoriesCount)
    MiscUtil.PrintInfo(
        "Number of  matched molecules containing core scaffold(s): %d" %
        RGroupsMolMatchedCount)
    MiscUtil.PrintInfo(
        "Number of  unmatched molecules containing no core scaffold(s): %d" %
        RGroupsMolUnmatchedCount)
def WriteHTMLPageHeader(Writer, MolCount):
    """Write out HTML page header."""

    ColCount = GetColCount(MolCount)
    
    # Exclude counter and structure columns from sorting and searching...
    if OptionsInfo["CounterCol"]:
        ColIndicesList = ["0"]
        ColVisibilityExcludeColIndicesList = ["0"]
        ColIndexOffset = 1
        FreezeLeftColumns = "1"
    else:
        ColIndicesList = []
        ColVisibilityExcludeColIndicesList = []
        ColIndexOffset = 0

    MaxDataColVisColCount = 25
    for Index in range(0, ColCount):
        ColIndex = Index + ColIndexOffset
        ColIndicesList.append("%s" % ColIndex)
        
        if OptionsInfo["ColVisibility"]:
            if Index >= MaxDataColVisColCount:
                ColVisibilityExcludeColIndicesList.append("%s" %ColIndex)
    
    ColIndices = MiscUtil.JoinWords(ColIndicesList, ", ") if  len(ColIndicesList) else ""
    ColVisibilityExcludeColIndices = MiscUtil.JoinWords(ColVisibilityExcludeColIndicesList, ", ") if len(ColVisibilityExcludeColIndicesList) else ""
        
    DataColVisibilityExclude = False
    if OptionsInfo["ColVisibility"]:
        if ColCount > MaxDataColVisColCount:
            DataColVisibilityExclude = True
            MiscUtil.PrintWarning("The number of data columns, %d, is quite large. Only first %d data columns will be available in column visibility pulldown." % (ColCount, MaxDataColVisColCount))
            
    DisplayButtons = False
    if OptionsInfo["ColVisibility"]:
        if ColCount > 0:
            DisplayButtons = True
    
    FreezeCols = False
    if (OptionsInfo["CounterCol"] and OptionsInfo["ScrollX"]):
        FreezeCols = True
    
    Paging = "true" if OptionsInfo["Paging"] else "false"
    PageLength = "%d" % OptionsInfo["PageLength"]
    PagingType = "\"%s\"" % OptionsInfo["PagingType"]

    ScrollX = "true" if OptionsInfo["ScrollX"] else "false"
    
    ScrollY = ""
    if OptionsInfo["ScrollY"]:
        if re.search("vh$", OptionsInfo["ScrollYSize"]):
            ScrollY = "\"%s\"" % OptionsInfo["ScrollYSize"]
        else:
            ScrollY = "%s" % OptionsInfo["ScrollYSize"]
    
    # Start HTML header...
    Title = "Molecules table" if OptionsInfo["Header"] is None else OptionsInfo["Header"]
    
    Writer.write("""\
<!doctype html>
<html lang="en">
<head>
    <title>%s</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
  
""" % (Title))

    if (FreezeCols):
        Writer.write("""\
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">
""")
    
    if (OptionsInfo["KeysNavigation"]):
        Writer.write("""\
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.3.2/css/keyTable.bootstrap4.min.css">
""")
    
    Writer.write("""\

    <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>

""")

    if (OptionsInfo["Popover"]):
        Writer.write("""\
    <script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
    <script type="text/javascript" language="javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>

""")
        
    if DisplayButtons:
        Writer.write("""\
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.colVis.min.js"></script>

""")
    
    if (FreezeCols):
        Writer.write("""\
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>
""")
    
    if (OptionsInfo["KeysNavigation"]):
        Writer.write("""\
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/keytable/2.3.2/js/dataTables.keyTable.min.js"></script>
""")
    
    # Intialize table using Bootstrap, DataTables and JQuery frameworks...
    Writer.write("""\

    <script type="text/javascript" class="init">

$(document).ready(function() {
""")

    if (OptionsInfo["Popover"]):
        Writer.write("""\
    $('.MolPopover').popover();

""")
        
    Writer.write("""\
    var MolsTable = $('#MolsTable').DataTable( {
        "columnDefs": [
            {
                "orderable": false,
                "searchable": false,
                "targets": [%s]
            },
""" % (ColIndices))

    if OptionsInfo["ColVisibility"]:
        Writer.write("""\
            {
                "className": "noColVisCtrl",
                "targets": [%s]
            }
""" % (ColVisibilityExcludeColIndices))

    Writer.write("""\
        ],
""")

    # Set up dom for displaying button and other options...
    if OptionsInfo["ColVisibility"]:
        if OptionsInfo["Paging"]:
            Writer.write("""\
        "dom":  "<'row'<'col-sm-6'l><'col-sm-6'<'float-right'B>>>" +
            "<'row'<'col-sm-12'tr>>" +
            "<'row'<'col-sm-5'i><'col-sm-7'p>>",
""")
        else:
            Writer.write("""\
        "dom":  "<'row'<'col'<'float-right'B>>>" +
            "<'row'<'col-sm-12'tr>>" +
            "<'row'<'col-sm-5'i><'col-sm-7'p>>",
""")
    else:
            Writer.write("""\
        "dom":  "<'row'<'col'l>>" +
            "<'row'<'col-sm-12'tr>>" +
            "<'row'<'col-sm-5'i><'col-sm-7'p>>",
""")
    
    #
    if OptionsInfo["ColVisibility"]:
        # Set up buttons...
        Writer.write("""\
        "buttons": [
            {
                "extend": "colvis",
                "text": "Column visibility",
                "className": "btn btn-outline-light text-dark",
                "columns": ":not(.noColVisCtrl)",
""")
        if not DataColVisibilityExclude:
            Writer.write("""\
                "prefixButtons": [ "colvisRestore" ],
""")
        
        Writer.write("""\
                "columnText": function ( dt, colIndex, colLabel ) {
                    return "Column " + (colIndex + 1);
                },
            }
        ],
""")
    
    # Write out rest of the variables for DataTables...
    if FreezeCols:
        Writer.write("""\
        "fixedColumns": {
            "leftColumns": %s
        },
""" % (FreezeLeftColumns))
    
    if (OptionsInfo["KeysNavigation"]):
        Writer.write("""\
        "keys": true,
""")
    
    Writer.write("""\
        "pageLength": %s,
        "lengthMenu": [ [5, 10, 15, 25, 50, 100, 500, 1000, -1], [5, 10, 15, 25, 50, 100, 500, 1000, "All"] ],
        "paging": %s,
        "pagingType": %s,
        "scrollX": %s,
        "scrollY": %s,
        "scrollCollapse": true,
        "order": [],
    } );
""" % (PageLength, Paging, PagingType, ScrollX, ScrollY))
    
    if OptionsInfo["CounterCol"]:
        Writer.write("""\
    MolsTable.on( 'order.dt search.dt', function () {
        MolsTable.column(0, {search:'applied', order:'applied'}).nodes().each( function (cell, rowIndex) {
            cell.innerHTML = rowIndex + 1;
        } );
    } ).draw();
""")
    
    # End of Javacscript code...
    Writer.write("""\
} );

    </script>
""")

    # Finish up HTML header...
    Writer.write("""\
  
</head>
<body>
  <div class="container-fluid">
    <br/>
""")