Make the public html pages valid xhtml, and validate more RDFa cases.
authorDiane Trout <diane@caltech.edu>
Mon, 24 Sep 2012 22:28:10 +0000 (15:28 -0700)
committerDiane Trout <diane@caltech.edu>
Mon, 24 Sep 2012 22:28:10 +0000 (15:28 -0700)
Also after I spent time playing with the w3c online validator,
I decided it was best to try and add modest validation to my
unit tests.

So now there's a validate_xhtml function in ethelp.

The one really weird thing is I tried to load the DTD
in the test case, however it looks like librdf clobbered the
XML catalog resolver at some point so the DTD resolver can't
find anything.

htsworkflow/frontend/experiments/tests.py
htsworkflow/frontend/samples/tests.py
htsworkflow/frontend/templates/base.html
htsworkflow/frontend/templates/experiments/flowcell_detail.html
htsworkflow/frontend/templates/experiments/flowcell_header.html
htsworkflow/frontend/templates/experiments/flowcell_lane_detail.html
htsworkflow/frontend/templates/sample_header.html
htsworkflow/frontend/templates/samples/library_detail.html
htsworkflow/util/ethelp.py
htsworkflow/util/schemas/htsworkflow.turtle

index 93e1f3d452c32a5c6b74bae709794e1340a8e96b..8f535c824bc26cdf87089a50331269104236e9b7 100644 (file)
@@ -17,6 +17,7 @@ from django.test import TestCase
 from htsworkflow.frontend.experiments import models
 from htsworkflow.frontend.experiments import experiments
 from htsworkflow.frontend.auth import apidata
+from htsworkflow.util.ethelp import validate_xhtml
 
 from htsworkflow.pipelines.test.simulate_runfolder import TESTDATA_DIR
 
@@ -256,6 +257,7 @@ class ExperimentsTestCases(TestCase):
                         u'11061',u'11062',u'11063',u'11064']
         self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/admin/experiments/flowcell/153/')
+
         tree = fromstring(response.content)
         for i in range(0,8):
             xpath_expression = '//input[@id="id_lane_set-%d-library"]'
@@ -274,6 +276,10 @@ class ExperimentsTestCases(TestCase):
         """
         self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
         response = self.client.get('/library/11070/')
+        self.assertEqual(response.status_code, 200)
+        status = validate_xhtml(response.content)
+        if status is not None: self.assertTrue(status)
+
         tree = fromstring(response.content)
         flowcell_spans = tree.xpath('//span[@property="libns:flowcell_id"]',
                                     namespaces=NSMAP)
@@ -285,8 +291,14 @@ class ExperimentsTestCases(TestCase):
         self.assertEqual(failed_fc_a.get('href'), '/flowcell/30012AAXX/')
         fc_response = self.client.get(failed_fc_a.get('href'))
         self.assertEqual(fc_response.status_code, 200)
+        status = validate_xhtml(response.content)
+        if status is not None: self.assertTrue(status)
+
         fc_lane_response = self.client.get('/flowcell/30012AAXX/8/')
         self.assertEqual(fc_lane_response.status_code, 200)
+        status = validate_xhtml(response.content)
+        if status is not None: self.assertTrue(status)
+
 
     def test_pooled_multiplex_id(self):
         fc_dict = experiments.flowcell_information('42JU1AAXX')
@@ -423,17 +435,20 @@ class ExperimentsTestCases(TestCase):
 
         model = get_model()
 
-        expected = {1: ['11034'],
-                    2: ['11036'],
-                    3: ['12044','11045'],
-                    4: ['11047','13044'],
-                    5: ['11055'],
-                    6: ['11067'],
-                    7: ['11069'],
-                    8: ['11070']}
+        expected = {'1': ['11034'],
+                    '2': ['11036'],
+                    '3': ['12044','11045'],
+                    '4': ['11047','13044'],
+                    '5': ['11055'],
+                    '6': ['11067'],
+                    '7': ['11069'],
+                    '8': ['11070']}
         url = '/flowcell/42JU1AAXX/'
         response = self.client.get(url)
         self.assertEqual(response.status_code, 200)
+        status = validate_xhtml(response.content)
+        if status is not None: self.assertTrue(status)
+
         ns = urljoin('http://localhost', url)
         load_string_into_model(model, 'rdfa', response.content, ns=ns)
         body = """prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
@@ -610,3 +625,51 @@ class TestSequencer(TestCase):
             './span[@property="libns:sequencer_model"]')
         self.assertEqual(len(model), 1)
         self.assertEqual(model[0].text, 'Illumina Genome Analyzer IIx')
+
+    def test_flowcell_with_rdf_validation(self):
+        from htsworkflow.util.rdfhelp import add_default_schemas, \
+             dump_model, \
+             get_model, \
+             load_string_into_model
+        from htsworkflow.util.rdfinfer import Infer
+
+        model = get_model()
+        add_default_schemas(model)
+        inference = Infer(model)
+
+        url ='/flowcell/FC12150/'
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        status = validate_xhtml(response.content)
+        if status is not None: self.assertTrue(status)
+
+        load_string_into_model(model, 'rdfa', response.content)
+
+        errmsgs = list(inference.run_validation())
+        self.assertEqual(len(errmsgs), 2)
+        for errmsg in errmsgs:
+            self.assertEqual(errmsg, 'Missing type for: http://localhost/')
+
+    def test_lane_with_rdf_validation(self):
+        from htsworkflow.util.rdfhelp import add_default_schemas, \
+             dump_model, \
+             get_model, \
+             load_string_into_model
+        from htsworkflow.util.rdfinfer import Infer
+
+        model = get_model()
+        add_default_schemas(model)
+        inference = Infer(model)
+
+        url = '/lane/1193'
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        status = validate_xhtml(response.content)
+        if status is not None: self.assertTrue(status)
+
+        load_string_into_model(model, 'rdfa', response.content)
+
+        errmsgs = list(inference.run_validation())
+        self.assertEqual(len(errmsgs), 2)
+        for errmsg in errmsgs:
+            self.assertEqual(errmsg, 'Missing type for: http://localhost/')
index daabc11fe9f9289b4055cb13395565d61bae3dae..bfe4b0e2c7260c6a5f18893c72c93708f80fde11 100644 (file)
@@ -20,7 +20,7 @@ from htsworkflow.frontend.samples.views import \
 
 from htsworkflow.frontend.auth import apidata
 from htsworkflow.util.conversion import unicode_or_none
-
+from htsworkflow.util.ethelp import validate_xhtml
 
 class LibraryTestCase(TestCase):
     fixtures = ['test_samples.json']
@@ -149,7 +149,8 @@ class SampleWebTestCase(TestCase):
 
         response = self.client.get('/library/10981/')
         self.assertEqual(response.status_code, 200)
-        load_string_into_model(model, 'rdfa', response.content)
+        content = response.content
+        load_string_into_model(model, 'rdfa', content)
 
         body = """prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
         prefix libns: <http://jumpgate.caltech.edu/wiki/LibraryOntology#>
@@ -170,6 +171,9 @@ class SampleWebTestCase(TestCase):
             self.assertEqual(fromTypedNode(r['gel_cut']), 400)
             self.assertEqual(fromTypedNode(r['made_by']), u'Igor')
 
+        state = validate_xhtml(content)
+        if state is not None: self.assertTrue(state)
+
     def test_library_index_rdfa(self):
         from htsworkflow.util.rdfhelp import \
              add_default_schemas, get_model, load_string_into_model
@@ -212,6 +216,8 @@ class SampleWebTestCase(TestCase):
 
         self.assertEqual(count, len(Library.objects.filter(hidden=False)))
 
+        state = validate_xhtml(response.content)
+        if state is not None: self.assertTrue(state)
 # The django test runner flushes the database between test suites not cases,
 # so to be more compatible with running via nose we flush the database tables
 # of interest before creating our sample data.
index e623d0d6f6672640f9afff19d418566cc6f5025f..f8899df9e453177629b945e9ff598ecd8edfa5ab 100644 (file)
@@ -3,7 +3,7 @@
     "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml"
       version="XHTML+RDFa 1.0"
-      xmlns:xml="http://www.w3.org/1999/xhtml"
+      xmlns:xml="http://www.w3.org/XML/1998/namespace"
       xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
       xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
       xmlns:dc="http://purl.org/dc/elements/1.1/"
@@ -67,7 +67,6 @@
         {{ content }}
         {% endblock %}
         {% block sidebar %}{% endblock %}
-        <br class="clear" />
     </div>
     <!-- END Content -->
 
index 3a3b736e9d445857c14a6be4e7889cd44fd6017b..4183c995ebe81cca7117769658bcbf95175286c6 100644 (file)
@@ -10,9 +10,9 @@
 {% endblock %}
 
 {% block content %}
-<div id="flowcell_detail">
+<div>
   {% include "experiments/flowcell_header.html" %}
-  <div class="htswdetail">
+  <div class="htswdetail" typeof="libns:IlluminaFlowcell" resource="{{flowcell.get_absolute_url}}">
     <h2>Lanes</h2>
     <table>
       <thead>
       </thead>
       <tbody>
       {% for lane in lanes %}
-        <tr rel="libns:has_lane">
-          <div typeof="libns:IlluminaLane" about="{{lane.get_absolute_url}}">
-          <td>
-            <a href="{{lane.get_absolute_url}}">
-              <span property="libns:lane_number" datatype="xsd:decimal"
-                    >{{lane.lane_number}}</span>
+        <tr>
+          <td rel="libns:has_lane">
+            <a href="{{lane.get_absolute_url}}" typeof="libns:IlluminaLane">
+              <span property="libns:lane_number">{{lane.lane_number}}</span>
             </a>
           </td>
-          <div rel="libns:library">
-          <div typeof="libns:Library" about="{{lane.library.get_absolute_url}}">
-          <td>
-            <a href="{{lane.library.get_absolute_url}}">
+          <td rel="libns:library"
+              about="{{lane.get_absolute_url}}"
+              resource="{{lane.library.get_absolute_url}}">
+            <a typeof="libns:Library" href="{{lane.library.get_absolute_url}}">
               <span property="libns:library_id"
                     >{{lane.library.id}}</span></a>
               {% if user.is_staff %}
                        src="/media/img/admin/icon_changelink.gif"/>
               </a>{% endif %}
           </td>
-          <td><a href="{{lane.library.get_absolute_url}}">
-              <span property="libns:name">{{lane.library.library_name}}</span>
-          </a></td>
-          <td rel="libns:species">
+          <td>
+            <a href="{{lane.library.get_absolute_url}}">
+              <span property="libns:name"
+                    about="{{lane.library.get_absolute_url}}"
+                    >{{lane.library.library_name}}</span>
+            </a>
+          </td>
+          <td about="{{lane.library.get_absolute_url}}" rel="libns:species">
             <a href="{{lane.library.library_species.get_absolute_url}}"
                typeof="libns:Species">
-              <span property="libns:species_name">{{ lane.library.library_species.scientific_name }}</span></a></td>
-          </div> <!-- end library class -->
-          </div> <!-- end library relation -->
-          <td><span property="libns:comment">{{lane.comment}}</span></td>
-          </div> <!-- end lane -->
+              <span property="libns:species_name">{{ lane.library.library_species.scientific_name }}</span></a>
+          </td>
+          <td about="{{lane.get_absolute_url}}">
+            <span property="libns:comment">{{lane.comment}}</span>
+          </td>
         </tr>
       {% endfor %}
       </tbody>
     </table>
-    </div>
     <div class="htsw_flowcell_ivc">
     {% for run in flowcell.datarun_set.all %}
        <h2>Run {{ run.runfolder_name }}</h2>
+       {% if run.lane_files %}
        <table>
          <thead>
            <tr>
              <td>IVC Percent Base</td>
              <td>IVC Percent Base All</td>
              <td>IVC Percent Base Called</td>
+           </tr>
          </thead>
          <tbody>
             {% for lane_id, lane_file_set in run.lane_files.items %}
-           {% if lane_file_set.ivc_all %}
+            {% if lane_file_set.ivc_all %}
             <tr>
               <td>{{ lane_id }}</td>
               <td>
                 <a href="{{ lane_file_set.ivc_all.get_absolute_url }}">
-                <img height="84" width="126" src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
+                <img height="84" width="126" alt="Lane {{lane_id }} IVC All"
+                     src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
               </td>
               <td>
                 <a href="{{ lane_file_set.ivc_call.get_absolute_url }}">
-                <img height="84" width="126" src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
+                <img height="84" width="126" alt="Lane {{lane_id }} IVC Call"
+                     src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
                 </a>
               </td>
               <td>
                 <a href="{{ lane_file_set.ivc_percent_base.get_absolute_url }}">
-                <img height="84" width="126" src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
+                <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base"
+                     src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
                 </a>
               </td>
               <td>
                 <a href="{{ lane_file_set.ivc_percent_all.get_absolute_url }}">
-                <img height="84" width="126" src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
+                <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base All"
+                     src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
                 </a>
               </td>
               <td>
                 <a href="{{ lane_file_set.ivc_percent_call.get_absolute_url }}">
-                <img height="84" width="126" src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
+                <img height="84" width="126"
+                     alt="Lane {{lane_id }} IVC % Base Called"
+                     src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
                 </a>
               </td>
             </tr>
             {% endfor %}
          </tbody>
        </table>
+       {% endif %}
     {% endfor %}
     </div>
+  </div>
 </div>
+<!-- end flowcell_detail -->
 {% endblock %}
index 412d3ac6b16ae4a02d4243f983787677f0bf9173..f71ae0fed0e77a05520b7e35c6f631004d437666 100644 (file)
@@ -1,8 +1,8 @@
-  <div class="flowcell_identity">
+<div class="flowcell_identity" typeof="libns:IlluminaFlowcell" resource="{{flowcell.get_absolute_url}}">
   <h2>About this Flowcell</h2>
   <b>Flowcell</b>:
-    <a href="{{flowcell.get_absolute_url}}" property="libns:flowcell_id">{{flowcell.flowcell_id}}</a>{% if user.is_staff %}<a href="{{flowcell.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif" alt="Edit"/></a>{% endif%}
-  <br rel="rdf:type" resource="http://jumpgate.caltech.edu/wiki/LibraryOntology#IlluminaFlowcell"/>
+    <a href="{{flowcell.get_absolute_url}}"><span property="libns:flowcell_id">{{flowcell.flowcell_id}}</span></a>{% if user.is_staff %}<a href="{{flowcell.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif" alt="Edit"/></a>{% endif%}
+  <br/>
   <div rel="libns:sequenced_by">
   <div typeof="libns:Sequencer"
        about="{{flowcell.sequencer.get_absolute_url}}">
   <b>Type</b>:
     <span property="libns:flowcell_type">{{flowcell.flowcell_type}}</span><br/>
   <b>Read Length</b>:
-    <span property="libns:read_length" datatype="xsd:decimal">{{flowcell.read_length}}</span><br/>
+    <span property="libns:read_length" datatype="xsd:integer">{{flowcell.read_length}}</span><br/>
   <b>Control Lane</b>:
-    <span property="libns:control_lane" datatype="xsd:decimal">{{flowcell.control_lane}}</span><br/>
+    <span property="libns:control_lane">{{flowcell.control_lane}}</span><br/>
 
   <b>Notes</b>:
     <pre property="libns:flowcell_notes">{{flowcell.notes}}</pre>
 </div>
+ </div>
index 33c47fb50d66edbbc8e51655cd06280e63e9af78..7e834eb21ec00f76954776885fab803c3261da9d 100644 (file)
 {% endblock %}
 
 {% block content %}
-<div id="lane_detail" class="htswdetail" rel="rdf:type" resource="libns:IlluminaLane">
+<div id="lane_detail" class="htswdetail" typeof="libns:IlluminaLane" resource="{{lane.get_absolute_url}}">
   <div rel="libns:flowcell" resource="{{flowcell.get_absolute_url}}">
   {% include "experiments/flowcell_header.html" %}
   </div>
   <div class="flowcell_lane_detail">
   <h2>About this lane</h2>
   <b>Lane</b>:
-    <span property="libns:lane_number" datatype="xsd:decimal">{{lane.lane_number}}</span><br/>
+    <span property="libns:lane_number">{{lane.lane_number}}</span><br/>
   <b>pM</b>:
     <span property="libns:pM" datatype="xsd:decimal">{{ lane.pM }}</span><br/>
+  {% if lane.cluster_estimate %}
   <b>Cluster Estimate</b>:
     <span property="libns:cluster_estimate" datatype="xsd:decimal"
-          content="{{lane.cluster_estimate}}">{{ lane.cluster_estimate|intcomma }}</span><br/>
+          content="{{lane.cluster_estimate}}">{{ lane.cluster_estimate|intcomma }}</span><br/>{% endif %}
+  {% if lane.status %}
   <b>Lane Status</b>:
-    <span property="libns:status">{{ lane.status }}</span><br/>
+    <span property="libns:status">{{ lane.status }}</span><br/>{% endif %}
+  {% if lane.comment %}
   <b>Comments</b>:
-    <span property="libns:comment">{{ lane.comment }}</span><br/>
+    <span property="libns:comment">{{ lane.comment }}</span><br/>{% endif %}
   </div>
   <hr/>
   {% include "sample_header.html" %}
             <td>{{lane_number}}</td>
             <td>
               <a href="{{ lane_file_set.ivc_all.get_absolute_url }}">
-              <img height="84" width="126" src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
+              <img height="84" width="126" alt="Lane {{lane_id }} IVC All"
+                   src="{{ lane_file_set.ivc_all.get_absolute_url }}"/></a>
             </td>
             <td>
               <a href="{{ lane_file_set.ivc_call.get_absolute_url }}">
-              <img height="84" width="126" src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
+              <img height="84" width="126" alt="Lane {{lane_id }} IVC Call"
+                   src="{{ lane_file_set.ivc_call.get_absolute_url }}"/>
               </a>
             </td>
             <td>
               <a href="{{ lane_file_set.ivc_percent_base.get_absolute_url }}">
-              <img height="84" width="126" src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
+              <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base"
+                   src="{{ lane_file_set.ivc_percent_base.get_absolute_url }}"/>
               </a>
             </td>
             <td>
               <a href="{{ lane_file_set.ivc_percent_all.get_absolute_url }}">
-              <img height="84" width="126" src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
+              <img height="84" width="126" alt="Lane {{lane_id }} IVC % Base All"
+                   src="{{ lane_file_set.ivc_percent_all.get_absolute_url }}"/>
               </a>
             </td>
             <td>
               <a href="{{ lane_file_set.ivc_percent_call.get_absolute_url }}">
-              <img height="84" width="126" src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
+              <img height="84" width="126"
+                   alt="Lane {{lane_id }} IVC % Base Called"
+                   src="{{ lane_file_set.ivc_percent_call.get_absolute_url }}"/>
               </a>
             </td>
           </tr>
+          {% else %}
+            <tr><td colspan="6">No data</td></tr>
           {% endif %}
           {% endfor %}
        </tbody>
index d38d9103e91e071f9210a31b118cc0e7f6771c2c..8dc14ee37d559ae13ba8cb6831f6b7a158567d43 100644 (file)
@@ -5,7 +5,7 @@
     <h2>Library Name</h2>
     <b>Library ID</b>:
        <a href="{{lib.get_absolute_url}}"><span property="libns:library_id">{{ lib.id }}</span></a>
-       {% if user.is_staff %}<a href="{{lib.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif"/></a>{% endif %}
+       {% if user.is_staff %}<a href="{{lib.get_admin_url}}"><img class="icon_button" src="/media/img/admin/icon_changelink.gif" alt="Edit"/></a>{% endif %}
        <br />
     <b>Name</b>:
       <span property="libns:name">{{ lib.library_name }}</span>
     <br/>
     {% if lib.gel_cut_size %}
     <b>Gel Cut Size</b>:
-      <span property="libns:gel_cut" datatype="xsd:decimal">{{ lib.gel_cut_size }}</span>
+      <span property="libns:gel_cut" datatype="xsd:integer">{{ lib.gel_cut_size }}</span>
     <br/>
     {% endif %}
     {% if lib.insert_size %}
     <b>Insert Size</b>:
-      <span property="libns:insert_size" datatype="xsd:decimal">{{ lib.insert_size }}</span>
+      <span property="libns:insert_size" datatype="xsd:integer">{{ lib.insert_size }}</span>
     <br/>
     {% endif %}
     {% if lib.undiluted_concentration %}
index b7a182e1961b4d1588554cc4c92e09adcc1ce835..9cc76f5b25279fe9f044d4c29fea0dfedd740a45 100644 (file)
@@ -28,6 +28,7 @@
     </tr>
   </thead>
   <tbody>
+  {% if eland_results %}
     {% for result in eland_results %}
     <tr about="{{result.flowcell.get_absolute_url}}">
       <td property="libns:date" content="{{result.run_date|date:'Y-m-d\TH:i:s'}}" datatype="xsd:dateTime">{{ result.run_date|date}}</td>
@@ -48,6 +49,9 @@
       </td>
     </tr>
     {% endfor %}
+  {% else %}
+    <tr><td colspan="8">No data</td></tr>
+  {% endif %}
   </tbody>
   </table>
 
@@ -85,7 +89,7 @@
       </tr>
     </thead>
     <tbody>
-
+    {% if lane_summary_list %}
       {# ls short for lane summary #}
       {% for ls in lane_summary_list %}
       <tr about="{{ls.lane.get_absolute_url}}">
         <td>{{ ls.repeat_reads|intcomma }}</td>
       </tr>
       {% endfor %}
-    </tbody>
-  </table>
+      {% else %}
+       <tr><td colspan="20">No data</td></tr>
+    {% endif %}
+  </tbody>
+    </table>
 
   <h2>Flowcell Notes</h2>
   <table>
        <td>Comment</td>
       </tr>
     </thead>
+    {% if lib.lane_set.all %}
     <tbody>
       {% for lane in lib.lane_set.all %}
       <tr rel="libns:has_lane" resource="{{lane.get_absolute_url}}">
       </tr>
          {% endfor %}
     </tbody>
+    {% endif %}
   </table>
   <br/>
   <hr/>
index 19f6c9f8536d612c29406b2204b8f40c6ea53adb..e4fe897342acfe656d63c3155a51c715610a9dca 100644 (file)
@@ -1,6 +1,15 @@
+"""ElementTree helper functions
 """
-ElementTree helper functions
-"""
+import logging
+import os
+LOGGER = logging.getLogger(__name__)
+
+import lxml.etree
+try:
+    XHTML_RDF_DTD = lxml.etree.DTD(external_id='-//W3C//DTD XHTML+RDFa 1.0//EN')
+except lxml.etree.DTDParseError as e:
+    LOGGER.warn("Unable to load XHTML DTD %s" % (str(e),))
+
 def indent(elem, level=0):
     """
     reformat an element tree to be 'pretty' (indented)
@@ -21,7 +30,7 @@ def indent(elem, level=0):
 
 def flatten(elem, include_tail=0):
     """
-    Extract the text from an element tree 
+    Extract the text from an element tree
     (AKA extract the text that not part of XML tags)
     """
     text = elem.text or ""
@@ -30,3 +39,36 @@ def flatten(elem, include_tail=0):
     if include_tail and elem.tail: text += elem.tail
     return text
 
+def validate_xhtml(html, base_url='http://localhost'):
+    """Helper for validating xhtml, mostly intended for test code
+
+    Defaults to assuming XHTML+RDFa
+    Returns None if there was a problem configuring validation
+    Logs messages from lxml.etree using python logging
+    Returns True if it passed validation
+    and False if it fails.
+    """
+    if XHTML_RDF_DTD is None:
+        return None
+
+    try:
+        root = lxml.etree.fromstring(html, base_url=base_url)
+    except lxml.etree.ParseError as e:
+        LOGGER.warn("Unable to parse document: %s" % (str(e),))
+        return False
+
+    if XHTML_RDF_DTD.validate(root):
+        # so unlikely.
+        return True
+
+    isgood = True
+    for msg in XHTML_RDF_DTD.error_log.filter_from_errors():
+        # I have no idea how to suppress this error
+        # but I need the xmlns attributes for of my RDFa 1.0 encoding
+        if 'ERROR:VALID:DTD_UNKNOWN_ATTRIBUTE' in str(msg):
+            continue
+        else:
+            LOGGER.error(msg)
+            isgood = False
+
+    return isgood
index 7319c0c1bb8bb8a14b32cb7a0436c8eaac42c0f9..92ed6e64e1dca4b62a47b9b5cd086aba9520a760 100644 (file)
@@ -193,6 +193,28 @@ htswlib:sequencer_name
     rdfs:domain htswlib:Sequencer ;
     rdfs:range rdfs:Literal .
 
+# lane properties
+htswlib:status
+   a rdf:Proprety ;
+   rdfs:comment "Operators opinion of lane status, e.g. were there spots in the pictures" ;
+   rdfs:label "Status" ;
+   rdfs:domain htswlib:IlluminaLane ;
+   rdfs:range htswlib:Literal .
+
+htswlib:cluster_estimate
+   a rdf:Proprety ;
+   rdfs:comment "Estimate of clusters per tile" ;
+   rdfs:label "Cluster Estimate" ;
+   rdfs:domain htswlib:IlluminaLane ;
+   rdfs:range htswlib:Literal .
+
+htswlib:pM
+   a rdf:Proprety ;
+   rdfs:comment "picoMolarity" ;
+   rdfs:label "picoMolarity" ;
+   rdfs:domain htswlib:IlluminaLane ;
+   rdfs:range htswlib:Literal .
+
 
 # library only properties
 htswlib:library_id
@@ -217,6 +239,13 @@ htswlib:library_type
     rdfs:domain htswlib:Library ;
     rdfs:range rdfs:Literal .
 
+htswlib:condition
+   a rdf:Property ;
+   rdfs:comment "Describes what treatment has been applied to the cells" ;
+   rdfs:label "Condition" ;
+   rdfs:domain htswlib:Library ;
+   rdfs:range rdfs:Literal .
+
 htswlib:stopping_point
     a rdf:Property ;
     rdfs:comment "Protocol stopping point" ;