Repository 'blast2html'
hg clone https://toolshed.g2.bx.psu.edu/repos/jankanis/blast2html

Changeset 22:efce16c98650 (2014-05-15)
Previous changeset 21:9596fea636bb (2014-05-15) Next changeset 23:6995a6f34f3f (2014-05-15)
Commit message:
rename blast2html
added:
blast2html.html.jinja
blast2html.py
removed:
blast_html.html.jinja
blast_html.py
b
diff -r 9596fea636bb -r efce16c98650 blast2html.html.jinja
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/blast2html.html.jinja Thu May 15 10:48:09 2014 +0200
[
b'@@ -0,0 +1,600 @@\n+<!DOCTYPE html>\n+<html>\n+  <head>\n+    <meta charset="UTF-8">\n+    <meta name=generator content="blast2html; see ...">\n+    \n+    <title>Blast output</title>\n+    \n+    <style>\n+      body {\n+      color: #333333;\n+      font-family: Arial,Sans-Serif;\n+      }\n+\n+      :link {\n+      color: #336699;\n+      }\n+\n+      .right {\n+      float: right;\n+      }\n+\n+      #content {\n+      margin: 0 2em;\n+      padding: 0.5em;\n+      border: 1px solid #888888;\n+      background-color: #d3dff5;\n+      }\n+\n+      h1, h2, h3, h4, h5, h6 {\n+      color: #2A6979;\n+      font-family: arial,verdana,sans-serif;\n+      letter-spacing: -1px;\n+      margin: 1.2em 0 0.3em;\n+      }\n+\n+      h1 {\n+      border-bottom: 1px solid #CCCCCC;\n+      font-size: 150%;\n+      padding-bottom: 0.1em;\n+      }\n+\n+      h2 {\n+      font-size: 120%;\n+      font-weight: bold;\n+      }\n+\n+      h4.darkHeader {\n+      color: #4D4D4D;\n+      letter-spacing: 0;\n+      font-weight: bold;\n+      }\n+\n+      #nodata {\n+      font-weight: bold;\n+      }\n+\n+      .index {\n+      margin-bottom: 3em;\n+      }\n+      .index div.indexentry {\n+      margin: 1.2em 1.6em;\n+      font-weight: bold;\n+      font-size: 100%;\n+      }\n+      \n+      .headerdata {\n+      font-size: 90%;\n+      }\n+      .headerdata .param {\n+      font-weight: bold;\n+      text-align: right;\n+      padding: 0 1em;\n+      }\n+\n+      .grey {\n+      background-color: #eeeeee;\n+      border: 1px solid #cccccc;\n+      padding: 1em;\n+      }\n+\n+      .white {\n+      background-color: white;\n+      border: 1px solid #cccccc;\n+      padding: 1.5em 2%;\n+      }\n+\n+      .graphicrow {\n+      clear: left;\n+      width: 100%;\n+      }\n+\n+      .graphicitem {\n+      float: left;\n+      }\n+\n+\n+      \n+      .graphics .grey {\n+      text-align: center;\n+      }\n+\n+      .graphic {\n+      background-color: white;\n+      border: 2px solid black;\n+      padding: 1.5em;\n+      margin: auto;\n+      }\n+\n+      .centered, .defline, div.legend, div.tablewrapper {\n+      margin-left: auto;\n+      margin-right: auto;\n+      }\n+\n+      .defline {\n+      background-color: white;\n+      border: 1px solid black;\n+      margin: .5em auto;\n+      padding-left: .2em;\n+      padding-right: .2em;\n+      max-width: 50em;\n+      text-align: left;\n+      height: 2.8em;\n+      overflow-y: hidden;\n+      }\n+\n+      div.legend {\n+      max-width: 40em;\n+      }\n+      div.legend div {\n+      width: 100%;\n+      color: white;\n+      font-weight: bold;\n+      border-spacing: 0;\n+      }\n+      div.legend div .graphicitem {\n+      width: 20%;\n+      padding: 0;\n+      margin: 0;\n+      border: none;\n+      }\n+\n+      div.tablewrapper {\n+      width: 50%;\n+      min-width: 60em;\n+      }\n+\n+      /* For small widths we give the graphic 100% */\n+      @media (max-width: 72.5em) {\n+      div.tablewrapper {\n+      width: 100%;\n+      min-width: 0px;\n+      }\n+      }\n+\n+      .scale {\n+      width: 100%;\n+      margin: .5em 0;\n+      font-weight: bold;\n+      }\n+      .scale div {\n+      color: red;\n+      text-align: left;\n+      }\n+      .scale .graphicrow {\n+      margin: .5em 0 .5em 0;\n+      color: white;\n+      }\n+      .scale .graphicitem {\n+      position: relative;\n+      }\n+      .scale .graphicitem div {\n+      margin: 0 1px;\n+      padding: 0 2px;\n+      text-align: right;\n+      background-color: red;\n+      color: white;\n+      }\n+      .scale .graphicitem:first-child div {\n+      margin-left: 0px;\n+      }\n+      .scale .graphicitem:last-child div {\n+      margin-right: 0px;\n+      }\n+      .scale .graphicitem .lastlabel {\n+      position: absolute;\n+      top: 0px;\n+      left: 100%;\n+      background-color: transparent;\n+      color: red;\n+      }\n+\n+      a.matchresult {\n+      display: block;\n+      margin: 0;\n+      padding: 0;\n+      }\n+      div.matchrow {\n+      margin-top: 4px;\n+      }\n+      div.matchrow, div.matchitem {\n+      height: 4px;\n+      }\n+\n+      \n+      table.descriptiontable {\n+      font-'..b'{{hit.link_id}}">\n+                        {{hit.title}}\n+                  </a></div></td>\n+                  <td>{{hit.maxscore}}</td>\n+                  <td>{{hit.totalscore}}</td>\n+                  <td>{{hit.cover}}</td>\n+                  <td>{{hit.e_value}}</td>\n+                  <td>{{hit.ident}}</td>\n+                  <td><a href="{{genelink(hit.hit|hitid)}}">{{hit.accession}}</a></td>\n+                </tr>\n+                {% endfor %}\n+              </table>\n+\n+          </div></div>\n+        </section>\n+\n+\n+\n+        <section class=alignments>\n+          <h2>Alignments</h2>\n+\n+          <div class=grey><div class=white>\n+              {% for hit in hits %}\n+              <div class=alignment id=hit{{hit.Hit_num}}>\n+\n+                <div class=linkheader>\n+                  <div class=right><a href="#description{{hit.Hit_num}}">Descriptions</a></div>\n+                  <a class=linkheader href="{{genelink(hit|hitid)}}">GenBank</a>\n+                  <a class=linkheader href="{{genelink(hit|hitid, \'graph\')}}">Graphics</a>\n+                </div>\n+\n+                <div class=title>\n+                  <p class=hittitle>{{hit|firsttitle}}</p>\n+                  <p class=titleinfo>\n+                    <span class=b>Sequence ID:</span> <a href="{{genelink(hit|hitid)}}">{{hit|seqid}}</a>\n+                    <span class=b>Length:</span> {{hit.Hit_len}}\n+                    <span class=b>Number of Matches:</span> {{hit.Hit_hsps.Hsp|length}}\n+                  </p>\n+                </div>\n+\n+                {% if hit|othertitles|length %}\n+                <a class=showmoretitles onclick="toggle_visibility(\'moretitles{{hit.Hit_num|js_string_escape}}\'); return false;" href=\'\'>\n+                  See {{hit|othertitles|length}} more title(s)\n+                </a>\n+\n+                <div class=moretitles id=moretitles{{hit.Hit_num}} style="display: none">\n+                  {% for title in hit|othertitles %}\n+                  <div class=title>\n+                    <p class=hittitle>{{title.title}}</p>\n+                    <p class=titleinfo>\n+                      <span class=b>Sequence ID:</span> <a href="{{genelink(title.hitid)}}">{{title.id}}</a>\n+                    </p>\n+                  </div>\n+                  {% endfor %}\n+                </div>\n+                {% endif %}\n+\n+                {% for hsp in hit.Hit_hsps.Hsp %}\n+                <div class=hotspot>\n+                  <p class=range>\n+                    <span class=range>Range {{hsp.Hsp_num}}: {{hsp[\'Hsp_hit-from\']}} to {{hsp[\'Hsp_hit-to\']}}</span>\n+                    <a class=range href="{{genelink(hit|hitid, \'genbank\', hsp)}}">GenBank</a>\n+                    <a class=range href="{{genelink(hit|hitid, \'graph\', hsp)}}">Graphics</a>\n+                  </p>\n+\n+                  <table class=hotspotstable>\n+                    <tr>\n+                      <th>Score</th><th>Expect</th><th>Identities</th><th>Gaps</th><th>Strand</th>\n+                    </tr>\n+                    <tr>\n+                      <td>{{hsp[\'Hsp_bit-score\']|fmt(\'.1f\')}} bits({{hsp.Hsp_score}})</td>\n+                      <td>{{hsp.Hsp_evalue|fmt(\'.1f\')}}</td>\n+                      <td>{{ hsp.Hsp_identity }}/{{ hsp|len }}({{\n+                        (hsp.Hsp_identity/hsp|len) |fmt(\'.0%\') }})</td>\n+                      <td>{{ hsp.Hsp_gaps }}/{{ hsp|len\n+                        }}({{ (hsp.Hsp_gaps / hsp|len) | fmt(\'.0%\') }})</td>\n+                      <td>{{ hsp[\'Hsp_query-frame\']|asframe }}/{{ hsp[\'Hsp_hit-frame\']|asframe }}</td>\n+                    </tr>\n+                  </table>\n+\n+                  <pre class=alignmentgraphic>{{hsp|alignment_pre}}</pre>\n+                </div>\n+                {% endfor %}\n+\n+              </div>\n+\n+              {% endfor %}\n+          </div></div>\n+        </section>\n+        {% endif %}\n+\n+        {% endfor %}\n+        {% endif %}\n+    </div>\n+  </body>\n+</html>\n+\n+{#\n+Local Variables:\n+tab-width: 2\n+indent-tabs-mode: nil\n+End:\n+#}\n'
b
diff -r 9596fea636bb -r efce16c98650 blast2html.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/blast2html.py Thu May 15 10:48:09 2014 +0200
[
b'@@ -0,0 +1,304 @@\n+#!/usr/bin/env python3\n+\n+# Copyright The Hyve B.V. 2014\n+# License: GPL version 3 or higher\n+\n+import sys\n+import math\n+import warnings\n+from os import path\n+from itertools import repeat\n+import argparse\n+from lxml import objectify\n+import jinja2\n+\n+\n+\n+_filters = {}\n+def filter(func_or_name):\n+    "Decorator to register a function as filter in the current jinja environment"\n+    if isinstance(func_or_name, str):\n+        def inner(func):\n+            _filters[func_or_name] = func.__name__\n+            return func\n+        return inner\n+    else:\n+        _filters[func_or_name.__name__] = func_or_name.__name__\n+        return func_or_name\n+\n+\n+def color_idx(length):\n+    if length < 40:\n+        return 0\n+    elif length < 50:\n+        return 1\n+    elif length < 80:\n+        return 2\n+    elif length < 200:\n+        return 3\n+    return 4\n+\n+@filter\n+def fmt(val, fmt):\n+    return format(float(val), fmt)\n+\n+@filter\n+def firsttitle(hit):\n+    return hit.Hit_def.text.split(\'>\')[0]\n+\n+@filter\n+def othertitles(hit):\n+    """Split a hit.Hit_def that contains multiple titles up, splitting out the hit ids from the titles."""\n+    id_titles = hit.Hit_def.text.split(\'>\')\n+\n+    titles = []\n+    for t in id_titles[1:]:\n+        fullid, title = t.split(\' \', 1)\n+        hitid, id = fullid.split(\'|\', 2)[1:3]\n+        titles.append(dict(id = id,\n+                           hitid = hitid,\n+                           fullid = fullid,\n+                           title = title))\n+    return titles\n+\n+@filter\n+def hitid(hit):\n+    return hit.Hit_id.text.split(\'|\', 2)[1]\n+\n+@filter\n+def seqid(hit):\n+    return hit.Hit_id.text.split(\'|\', 2)[2]\n+\n+@filter\n+def alignment_pre(hsp):\n+    return (\n+        "Query  {:>7s}  {}  {}\\n".format(hsp[\'Hsp_query-from\'].text, hsp.Hsp_qseq, hsp[\'Hsp_query-to\']) +\n+        "       {:7s}  {}\\n".format(\'\', hsp.Hsp_midline) +\n+        "Subject{:>7s}  {}  {}".format(hsp[\'Hsp_hit-from\'].text, hsp.Hsp_hseq, hsp[\'Hsp_hit-to\'])\n+    )\n+\n+@filter(\'len\')\n+def blastxml_len(node):\n+    if node.tag == \'Hsp\':\n+        return int(node[\'Hsp_align-len\'])\n+    elif node.tag == \'Iteration\':\n+        return int(node[\'Iteration_query-len\'])\n+    raise Exception("Unknown XML node type: "+node.tag)\n+        \n+\n+@filter\n+def asframe(frame):\n+    if frame == 1:\n+        return \'Plus\'\n+    elif frame == -1:\n+        return \'Minus\'\n+    raise Exception("frame should be either +1 or -1")\n+\n+def genelink(hit, type=\'genbank\', hsp=None):\n+    if not isinstance(hit, str):\n+        hit = hitid(hit)\n+    link = "http://www.ncbi.nlm.nih.gov/nucleotide/{}?report={}&log$=nuclalign".format(hit, type)\n+    if hsp != None:\n+        link += "&from={}&to={}".format(hsp[\'Hsp_hit-from\'], hsp[\'Hsp_hit-to\'])\n+    return link\n+\n+\n+# javascript escape filter based on Django\'s, from https://github.com/dsissitka/khan-website/blob/master/templatefilters.py#L112-139\n+# I\'ve removed the html escapes, since html escaping is already being performed by the template engine.\n+\n+_base_js_escapes = (\n+    (\'\\\\\', r\'\\u005C\'),\n+    (\'\\\'\', r\'\\u0027\'),\n+    (\'"\', r\'\\u0022\'),\n+    # (\'>\', r\'\\u003E\'),\n+    # (\'<\', r\'\\u003C\'),\n+    # (\'&\', r\'\\u0026\'),\n+    # (\'=\', r\'\\u003D\'),\n+    # (\'-\', r\'\\u002D\'),\n+    # (\';\', r\'\\u003B\'),\n+    # (u\'\\u2028\', r\'\\u2028\'),\n+    # (u\'\\u2029\', r\'\\u2029\')\n+)\n+\n+# Escape every ASCII character with a value less than 32. This is\n+# needed a.o. to prevent html parsers from jumping out of javascript\n+# parsing mode.\n+_js_escapes = (_base_js_escapes +\n+               tuple((\'%c\' % z, \'\\\\u%04X\' % z) for z in range(32)))\n+\n+@filter\n+def js_string_escape(value):\n+    """Escape javascript string literal escapes. Note that this only works\n+    within javascript string literals, not in general javascript\n+    snippets."""\n+\n+    value = str(value)\n+\n+    for bad, good in _js_escapes:\n+        value = value.replace(bad, good)\n+\n+    return value\n+\n+@filter\n+def hits(result):\n+    # sort hits by longest hotspot first\n+    return sorted(result.'..b'ange(query_length):\n+                if table[i] == last:\n+                    count += 1\n+                    continue\n+                matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n+                last = table[i]\n+                count = 1\n+            matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n+\n+            yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))\n+\n+    @filter\n+    def queryscale(self, result):\n+        query_length = blastxml_len(result)\n+        skip = math.ceil(query_length / self.max_scale_labels)\n+        percent_multiplier = 100 / query_length\n+        for i in range(1, query_length+1):\n+            if i % skip == 0:\n+                yield dict(label = i, width = skip * percent_multiplier, shorter = False\\)\n+        if query_length % skip != 0:\n+            yield dict(label = query_length,\n+                       width = (query_length % skip) * percent_multiplier,\n+                       shorter = True)\n+\n+    @filter\n+    def hit_info(self, result):\n+\n+        query_length = blastxml_len(result)\n+\n+        for hit in hits(result):\n+            hsps = hit.Hit_hsps.Hsp\n+\n+            cover = [False] * query_length\n+            for hsp in hsps:\n+                cover[hsp[\'Hsp_query-from\']-1 : int(hsp[\'Hsp_query-to\'])] = repeat(True, blastxml_len(hsp))\n+            cover_count = cover.count(True)\n+\n+            def hsp_val(path):\n+                return (float(hsp[path]) for hsp in hsps)\n+\n+            yield dict(hit = hit,\n+                       title = firsttitle(hit),\n+                       link_id = hit.Hit_num,\n+                       maxscore = "{:.1f}".format(max(hsp_val(\'Hsp_bit-score\'))),\n+                       totalscore = "{:.1f}".format(sum(hsp_val(\'Hsp_bit-score\'))),\n+                       cover = "{:.0%}".format(cover_count / query_length),\n+                       e_value = "{:.4g}".format(min(hsp_val(\'Hsp_evalue\'))),\n+                       # FIXME: is this the correct formula vv?\n+                       ident = "{:.0%}".format(float(min(hsp.Hsp_identity / blastxml_len(hsp) for hsp in hsps))),\n+                       accession = hit.Hit_accession)\n+\n+def main():\n+\n+    parser = argparse.ArgumentParser(description="Convert a BLAST XML result into a nicely readable html page",\n+                                     usage="{} [-i] INPUT [-o OUTPUT]".format(sys.argv[0]))\n+    input_group = parser.add_mutually_exclusive_group(required=True)\n+    input_group.add_argument(\'positional_arg\', metavar=\'INPUT\', nargs=\'?\', type=argparse.FileType(mode=\'r\'),\n+                             help=\'The input Blast XML file, same as -i/--input\')\n+    input_group.add_argument(\'-i\', \'--input\', type=argparse.FileType(mode=\'r\'), \n+                             help=\'The input Blast XML file\')\n+    parser.add_argument(\'-o\', \'--output\', type=argparse.FileType(mode=\'w\'), default=sys.stdout,\n+                        help=\'The output html file\')\n+    # We just want the file name here, so jinja can open the file\n+    # itself. But it is easier to just use a FileType so argparse can\n+    # handle the errors. This introduces a small race condition when\n+    # jinja later tries to re-open the template file, but we don\'t\n+    # care too much.\n+    parser.add_argument(\'--template\', type=argparse.FileType(mode=\'r\'), default=\'blast2html.html.jinja\',\n+                        help=\'The template file to use. Defaults to blast_html.html.jinja\')\n+\n+    args = parser.parse_args()\n+    if args.input == None:\n+        args.input = args.positional_arg\n+    if args.input == None:\n+        parser.error(\'no input specified\')\n+\n+    templatedir, templatename = path.split(args.template.name)\n+    args.template.close()\n+    if not templatedir:\n+        templatedir = \'.\'\n+\n+    b = BlastVisualize(args.input, templatedir, templatename)\n+    b.render(args.output)\n+\n+\n+if __name__ == \'__main__\':\n+    main()\n+\n'
b
diff -r 9596fea636bb -r efce16c98650 blast_html.html.jinja
--- a/blast_html.html.jinja Thu May 15 10:45:34 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
[
b'@@ -1,600 +0,0 @@\n-<!DOCTYPE html>\n-<html>\n-  <head>\n-    <meta charset="UTF-8">\n-    <meta name=generator content="blast_html; see ...">\n-    \n-    <title>Blast output</title>\n-    \n-    <style>\n-      body {\n-      color: #333333;\n-      font-family: Arial,Sans-Serif;\n-      }\n-\n-      :link {\n-      color: #336699;\n-      }\n-\n-      .right {\n-      float: right;\n-      }\n-\n-      #content {\n-      margin: 0 2em;\n-      padding: 0.5em;\n-      border: 1px solid #888888;\n-      background-color: #d3dff5;\n-      }\n-\n-      h1, h2, h3, h4, h5, h6 {\n-      color: #2A6979;\n-      font-family: arial,verdana,sans-serif;\n-      letter-spacing: -1px;\n-      margin: 1.2em 0 0.3em;\n-      }\n-\n-      h1 {\n-      border-bottom: 1px solid #CCCCCC;\n-      font-size: 150%;\n-      padding-bottom: 0.1em;\n-      }\n-\n-      h2 {\n-      font-size: 120%;\n-      font-weight: bold;\n-      }\n-\n-      h4.darkHeader {\n-      color: #4D4D4D;\n-      letter-spacing: 0;\n-      font-weight: bold;\n-      }\n-\n-      #nodata {\n-      font-weight: bold;\n-      }\n-\n-      .index {\n-      margin-bottom: 3em;\n-      }\n-      .index div.indexentry {\n-      margin: 1.2em 1.6em;\n-      font-weight: bold;\n-      font-size: 100%;\n-      }\n-      \n-      .headerdata {\n-      font-size: 90%;\n-      }\n-      .headerdata .param {\n-      font-weight: bold;\n-      text-align: right;\n-      padding: 0 1em;\n-      }\n-\n-      .grey {\n-      background-color: #eeeeee;\n-      border: 1px solid #cccccc;\n-      padding: 1em;\n-      }\n-\n-      .white {\n-      background-color: white;\n-      border: 1px solid #cccccc;\n-      padding: 1.5em 2%;\n-      }\n-\n-      .graphicrow {\n-      clear: left;\n-      width: 100%;\n-      }\n-\n-      .graphicitem {\n-      float: left;\n-      }\n-\n-\n-      \n-      .graphics .grey {\n-      text-align: center;\n-      }\n-\n-      .graphic {\n-      background-color: white;\n-      border: 2px solid black;\n-      padding: 1.5em;\n-      margin: auto;\n-      }\n-\n-      .centered, .defline, div.legend, div.tablewrapper {\n-      margin-left: auto;\n-      margin-right: auto;\n-      }\n-\n-      .defline {\n-      background-color: white;\n-      border: 1px solid black;\n-      margin: .5em auto;\n-      padding-left: .2em;\n-      padding-right: .2em;\n-      max-width: 50em;\n-      text-align: left;\n-      height: 2.8em;\n-      overflow-y: hidden;\n-      }\n-\n-      div.legend {\n-      max-width: 40em;\n-      }\n-      div.legend div {\n-      width: 100%;\n-      color: white;\n-      font-weight: bold;\n-      border-spacing: 0;\n-      }\n-      div.legend div .graphicitem {\n-      width: 20%;\n-      padding: 0;\n-      margin: 0;\n-      border: none;\n-      }\n-\n-      div.tablewrapper {\n-      width: 50%;\n-      min-width: 60em;\n-      }\n-\n-      /* For small widths we give the graphic 100% */\n-      @media (max-width: 72.5em) {\n-      div.tablewrapper {\n-      width: 100%;\n-      min-width: 0px;\n-      }\n-      }\n-\n-      .scale {\n-      width: 100%;\n-      margin: .5em 0;\n-      font-weight: bold;\n-      }\n-      .scale div {\n-      color: red;\n-      text-align: left;\n-      }\n-      .scale .graphicrow {\n-      margin: .5em 0 .5em 0;\n-      color: white;\n-      }\n-      .scale .graphicitem {\n-      position: relative;\n-      }\n-      .scale .graphicitem div {\n-      margin: 0 1px;\n-      padding: 0 2px;\n-      text-align: right;\n-      background-color: red;\n-      color: white;\n-      }\n-      .scale .graphicitem:first-child div {\n-      margin-left: 0px;\n-      }\n-      .scale .graphicitem:last-child div {\n-      margin-right: 0px;\n-      }\n-      .scale .graphicitem .lastlabel {\n-      position: absolute;\n-      top: 0px;\n-      left: 100%;\n-      background-color: transparent;\n-      color: red;\n-      }\n-\n-      a.matchresult {\n-      display: block;\n-      margin: 0;\n-      padding: 0;\n-      }\n-      div.matchrow {\n-      margin-top: 4px;\n-      }\n-      div.matchrow, div.matchitem {\n-      height: 4px;\n-      }\n-\n-      \n-      table.descriptiontable {\n-      font-'..b'{{hit.link_id}}">\n-                        {{hit.title}}\n-                  </a></div></td>\n-                  <td>{{hit.maxscore}}</td>\n-                  <td>{{hit.totalscore}}</td>\n-                  <td>{{hit.cover}}</td>\n-                  <td>{{hit.e_value}}</td>\n-                  <td>{{hit.ident}}</td>\n-                  <td><a href="{{genelink(hit.hit|hitid)}}">{{hit.accession}}</a></td>\n-                </tr>\n-                {% endfor %}\n-              </table>\n-\n-          </div></div>\n-        </section>\n-\n-\n-\n-        <section class=alignments>\n-          <h2>Alignments</h2>\n-\n-          <div class=grey><div class=white>\n-              {% for hit in hits %}\n-              <div class=alignment id=hit{{hit.Hit_num}}>\n-\n-                <div class=linkheader>\n-                  <div class=right><a href="#description{{hit.Hit_num}}">Descriptions</a></div>\n-                  <a class=linkheader href="{{genelink(hit|hitid)}}">GenBank</a>\n-                  <a class=linkheader href="{{genelink(hit|hitid, \'graph\')}}">Graphics</a>\n-                </div>\n-\n-                <div class=title>\n-                  <p class=hittitle>{{hit|firsttitle}}</p>\n-                  <p class=titleinfo>\n-                    <span class=b>Sequence ID:</span> <a href="{{genelink(hit|hitid)}}">{{hit|seqid}}</a>\n-                    <span class=b>Length:</span> {{hit.Hit_len}}\n-                    <span class=b>Number of Matches:</span> {{hit.Hit_hsps.Hsp|length}}\n-                  </p>\n-                </div>\n-\n-                {% if hit|othertitles|length %}\n-                <a class=showmoretitles onclick="toggle_visibility(\'moretitles{{hit.Hit_num|js_string_escape}}\'); return false;" href=\'\'>\n-                  See {{hit|othertitles|length}} more title(s)\n-                </a>\n-\n-                <div class=moretitles id=moretitles{{hit.Hit_num}} style="display: none">\n-                  {% for title in hit|othertitles %}\n-                  <div class=title>\n-                    <p class=hittitle>{{title.title}}</p>\n-                    <p class=titleinfo>\n-                      <span class=b>Sequence ID:</span> <a href="{{genelink(title.hitid)}}">{{title.id}}</a>\n-                    </p>\n-                  </div>\n-                  {% endfor %}\n-                </div>\n-                {% endif %}\n-\n-                {% for hsp in hit.Hit_hsps.Hsp %}\n-                <div class=hotspot>\n-                  <p class=range>\n-                    <span class=range>Range {{hsp.Hsp_num}}: {{hsp[\'Hsp_hit-from\']}} to {{hsp[\'Hsp_hit-to\']}}</span>\n-                    <a class=range href="{{genelink(hit|hitid, \'genbank\', hsp)}}">GenBank</a>\n-                    <a class=range href="{{genelink(hit|hitid, \'graph\', hsp)}}">Graphics</a>\n-                  </p>\n-\n-                  <table class=hotspotstable>\n-                    <tr>\n-                      <th>Score</th><th>Expect</th><th>Identities</th><th>Gaps</th><th>Strand</th>\n-                    </tr>\n-                    <tr>\n-                      <td>{{hsp[\'Hsp_bit-score\']|fmt(\'.1f\')}} bits({{hsp.Hsp_score}})</td>\n-                      <td>{{hsp.Hsp_evalue|fmt(\'.1f\')}}</td>\n-                      <td>{{ hsp.Hsp_identity }}/{{ hsp|len }}({{\n-                        (hsp.Hsp_identity/hsp|len) |fmt(\'.0%\') }})</td>\n-                      <td>{{ hsp.Hsp_gaps }}/{{ hsp|len\n-                        }}({{ (hsp.Hsp_gaps / hsp|len) | fmt(\'.0%\') }})</td>\n-                      <td>{{ hsp[\'Hsp_query-frame\']|asframe }}/{{ hsp[\'Hsp_hit-frame\']|asframe }}</td>\n-                    </tr>\n-                  </table>\n-\n-                  <pre class=alignmentgraphic>{{hsp|alignment_pre}}</pre>\n-                </div>\n-                {% endfor %}\n-\n-              </div>\n-\n-              {% endfor %}\n-          </div></div>\n-        </section>\n-        {% endif %}\n-\n-        {% endfor %}\n-        {% endif %}\n-    </div>\n-  </body>\n-</html>\n-\n-{#\n-Local Variables:\n-tab-width: 2\n-indent-tabs-mode: nil\n-End:\n-#}\n'
b
diff -r 9596fea636bb -r efce16c98650 blast_html.py
--- a/blast_html.py Thu May 15 10:45:34 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
[
b'@@ -1,304 +0,0 @@\n-#!/usr/bin/env python3\n-\n-# Copyright The Hyve B.V. 2014\n-# License: GPL version 3 or higher\n-\n-import sys\n-import math\n-import warnings\n-from os import path\n-from itertools import repeat\n-import argparse\n-from lxml import objectify\n-import jinja2\n-\n-\n-\n-_filters = {}\n-def filter(func_or_name):\n-    "Decorator to register a function as filter in the current jinja environment"\n-    if isinstance(func_or_name, str):\n-        def inner(func):\n-            _filters[func_or_name] = func.__name__\n-            return func\n-        return inner\n-    else:\n-        _filters[func_or_name.__name__] = func_or_name.__name__\n-        return func_or_name\n-\n-\n-def color_idx(length):\n-    if length < 40:\n-        return 0\n-    elif length < 50:\n-        return 1\n-    elif length < 80:\n-        return 2\n-    elif length < 200:\n-        return 3\n-    return 4\n-\n-@filter\n-def fmt(val, fmt):\n-    return format(float(val), fmt)\n-\n-@filter\n-def firsttitle(hit):\n-    return hit.Hit_def.text.split(\'>\')[0]\n-\n-@filter\n-def othertitles(hit):\n-    """Split a hit.Hit_def that contains multiple titles up, splitting out the hit ids from the titles."""\n-    id_titles = hit.Hit_def.text.split(\'>\')\n-\n-    titles = []\n-    for t in id_titles[1:]:\n-        fullid, title = t.split(\' \', 1)\n-        hitid, id = fullid.split(\'|\', 2)[1:3]\n-        titles.append(dict(id = id,\n-                           hitid = hitid,\n-                           fullid = fullid,\n-                           title = title))\n-    return titles\n-\n-@filter\n-def hitid(hit):\n-    return hit.Hit_id.text.split(\'|\', 2)[1]\n-\n-@filter\n-def seqid(hit):\n-    return hit.Hit_id.text.split(\'|\', 2)[2]\n-\n-@filter\n-def alignment_pre(hsp):\n-    return (\n-        "Query  {:>7s}  {}  {}\\n".format(hsp[\'Hsp_query-from\'].text, hsp.Hsp_qseq, hsp[\'Hsp_query-to\']) +\n-        "       {:7s}  {}\\n".format(\'\', hsp.Hsp_midline) +\n-        "Subject{:>7s}  {}  {}".format(hsp[\'Hsp_hit-from\'].text, hsp.Hsp_hseq, hsp[\'Hsp_hit-to\'])\n-    )\n-\n-@filter(\'len\')\n-def blastxml_len(node):\n-    if node.tag == \'Hsp\':\n-        return int(node[\'Hsp_align-len\'])\n-    elif node.tag == \'Iteration\':\n-        return int(node[\'Iteration_query-len\'])\n-    raise Exception("Unknown XML node type: "+node.tag)\n-        \n-\n-@filter\n-def asframe(frame):\n-    if frame == 1:\n-        return \'Plus\'\n-    elif frame == -1:\n-        return \'Minus\'\n-    raise Exception("frame should be either +1 or -1")\n-\n-def genelink(hit, type=\'genbank\', hsp=None):\n-    if not isinstance(hit, str):\n-        hit = hitid(hit)\n-    link = "http://www.ncbi.nlm.nih.gov/nucleotide/{}?report={}&log$=nuclalign".format(hit, type)\n-    if hsp != None:\n-        link += "&from={}&to={}".format(hsp[\'Hsp_hit-from\'], hsp[\'Hsp_hit-to\'])\n-    return link\n-\n-\n-# javascript escape filter based on Django\'s, from https://github.com/dsissitka/khan-website/blob/master/templatefilters.py#L112-139\n-# I\'ve removed the html escapes, since html escaping is already being performed by the template engine.\n-\n-_base_js_escapes = (\n-    (\'\\\\\', r\'\\u005C\'),\n-    (\'\\\'\', r\'\\u0027\'),\n-    (\'"\', r\'\\u0022\'),\n-    # (\'>\', r\'\\u003E\'),\n-    # (\'<\', r\'\\u003C\'),\n-    # (\'&\', r\'\\u0026\'),\n-    # (\'=\', r\'\\u003D\'),\n-    # (\'-\', r\'\\u002D\'),\n-    # (\';\', r\'\\u003B\'),\n-    # (u\'\\u2028\', r\'\\u2028\'),\n-    # (u\'\\u2029\', r\'\\u2029\')\n-)\n-\n-# Escape every ASCII character with a value less than 32. This is\n-# needed a.o. to prevent html parsers from jumping out of javascript\n-# parsing mode.\n-_js_escapes = (_base_js_escapes +\n-               tuple((\'%c\' % z, \'\\\\u%04X\' % z) for z in range(32)))\n-\n-@filter\n-def js_string_escape(value):\n-    """Escape javascript string literal escapes. Note that this only works\n-    within javascript string literals, not in general javascript\n-    snippets."""\n-\n-    value = str(value)\n-\n-    for bad, good in _js_escapes:\n-        value = value.replace(bad, good)\n-\n-    return value\n-\n-@filter\n-def hits(result):\n-    # sort hits by longest hotspot first\n-    return sorted(result.'..b'range(query_length):\n-                if table[i] == last:\n-                    count += 1\n-                    continue\n-                matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n-                last = table[i]\n-                count = 1\n-            matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n-\n-            yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))\n-\n-    @filter\n-    def queryscale(self, result):\n-        query_length = blastxml_len(result)\n-        skip = math.ceil(query_length / self.max_scale_labels)\n-        percent_multiplier = 100 / query_length\n-        for i in range(1, query_length+1):\n-            if i % skip == 0:\n-                yield dict(label = i, width = skip * percent_multiplier, shorter = False)\n-        if query_length % skip != 0:\n-            yield dict(label = query_length,\n-                       width = (query_length % skip) * percent_multiplier,\n-                       shorter = True)\n-\n-    @filter\n-    def hit_info(self, result):\n-\n-        query_length = blastxml_len(result)\n-\n-        for hit in hits(result):\n-            hsps = hit.Hit_hsps.Hsp\n-\n-            cover = [False] * query_length\n-            for hsp in hsps:\n-                cover[hsp[\'Hsp_query-from\']-1 : int(hsp[\'Hsp_query-to\'])] = repeat(True, blastxml_len(hsp))\n-            cover_count = cover.count(True)\n-\n-            def hsp_val(path):\n-                return (float(hsp[path]) for hsp in hsps)\n-\n-            yield dict(hit = hit,\n-                       title = firsttitle(hit),\n-                       link_id = hit.Hit_num,\n-                       maxscore = "{:.1f}".format(max(hsp_val(\'Hsp_bit-score\'))),\n-                       totalscore = "{:.1f}".format(sum(hsp_val(\'Hsp_bit-score\'))),\n-                       cover = "{:.0%}".format(cover_count / query_length),\n-                       e_value = "{:.4g}".format(min(hsp_val(\'Hsp_evalue\'))),\n-                       # FIXME: is this the correct formula vv?\n-                       ident = "{:.0%}".format(float(min(hsp.Hsp_identity / blastxml_len(hsp) for hsp in hsps))),\n-                       accession = hit.Hit_accession)\n-\n-def main():\n-\n-    parser = argparse.ArgumentParser(description="Convert a BLAST XML result into a nicely readable html page",\n-                                     usage="{} [-i] INPUT [-o OUTPUT]".format(sys.argv[0]))\n-    input_group = parser.add_mutually_exclusive_group(required=True)\n-    input_group.add_argument(\'positional_arg\', metavar=\'INPUT\', nargs=\'?\', type=argparse.FileType(mode=\'r\'),\n-                             help=\'The input Blast XML file, same as -i/--input\')\n-    input_group.add_argument(\'-i\', \'--input\', type=argparse.FileType(mode=\'r\'), \n-                             help=\'The input Blast XML file\')\n-    parser.add_argument(\'-o\', \'--output\', type=argparse.FileType(mode=\'w\'), default=sys.stdout,\n-                        help=\'The output html file\')\n-    # We just want the file name here, so jinja can open the file\n-    # itself. But it is easier to just use a FileType so argparse can\n-    # handle the errors. This introduces a small race condition when\n-    # jinja later tries to re-open the template file, but we don\'t\n-    # care too much.\n-    parser.add_argument(\'--template\', type=argparse.FileType(mode=\'r\'), default=\'blast_html.html.jinja\',\n-                        help=\'The template file to use. Defaults to blast_html.html.jinja\')\n-\n-    args = parser.parse_args()\n-    if args.input == None:\n-        args.input = args.positional_arg\n-    if args.input == None:\n-        parser.error(\'no input specified\')\n-\n-    templatedir, templatename = path.split(args.template.name)\n-    args.template.close()\n-    if not templatedir:\n-        templatedir = \'.\'\n-\n-    b = BlastVisualize(args.input, templatedir, templatename)\n-    b.render(args.output)\n-\n-\n-if __name__ == \'__main__\':\n-    main()\n-\n'