|
|
(2 intermediate revisions by 2 users not shown) |
Line 2: |
Line 2: |
| {{Freiburg/Menubar}} | | {{Freiburg/Menubar}} |
| | | |
− | {{Freiburg/wiki_content_start}} | + | {{Team:Freiburg/wiki_content_start_bubble}} |
| <html> | | <html> |
| <style> | | <style> |
Line 25: |
Line 25: |
| </style> | | </style> |
| | | |
− | <!--========= BEGIN: GoBack button ==========--> | + | <script type="text/javascript"> |
− | <div class="button_back">
| + | //===================BEGIN:Amazing Bubble Sidebar========================== |
− | <a style="color:#FFF" href="https://2015.igem.org/Team:Freiburg/Notebook"> << Back</a>
| + | |
− | </div>
| + | $(document).ready(function(){ |
− | <!--========= END: GoBack button ==========-->
| + | // CHANGE THE FOLLOWING ATTRIBUTES // |
| + | var href_text1='https://2015.igem.org/Team:Freiburg/Notebook', |
| + | // Text2 needs no href as it is the actual page // |
| + | img_url='https://static.igem.org/mediawiki/2015/9/99/Freibur_icon_notebook_white_03.png', |
| + | href_text3='https://2015.igem.org/Team:Freiburg/Glossary', |
| + | // Text1 needs no text as it is a pic // |
| + | text2='Wiki-Tricks'; |
| + | // Text3 needs no text as its always 'next' // |
| + | // HOLD ON CHANGING THINGS --JABBERWOCK // |
| + | |
| + | $('#bubble1').attr('href',href_text1); |
| + | $('#bubble1_img').attr('src', img_url); |
| + | $('#bubble3').attr('href',href_text3); |
| + | |
| + | $('#bubble2').text(text2); |
| + | }); |
| | | |
| + | //===================END:Amazing Bubble Sidebar========================== |
| + | </script> |
| | | |
| <div class="content_box"> | | <div class="content_box"> |
Line 63: |
Line 80: |
| </div> | | </div> |
| <p> | | <p> |
− | Even though the dokuwiki provides a feature to export the page's body content (by appending <code>&do=export_xhtmlbody</code> to the <acronym title="Uniform Resource Locator">URL</acronym> and getting the source code of this via <code>STRG+U</code> in Firefox) all the image links refer to the internal server and are, therefore, broken. The first step in migrating our content, thus was a script to download all the images of a specified internal wiki page and prefix the file names with <code>Freiburg_</code> to avoid name conflicts with other teams. The program also outputs a file with all the file names separated by commas that is later used to upload the files. | + | Even though the dokuwiki provides a feature to export the page's body content (by appending &do=export_xhtmlbody to the <acronym title="Uniform Resource Locator">URL</acronym> and getting the source code of this via STRG+U in Firefox) all the image links refer to the internal server and are, therefore, broken. The first step in migrating our content, thus was a script to download all the images of a specified internal wiki page and prefix the file names with Freiburg_ to avoid name conflicts with other teams. The program also outputs a file with all the file names separated by commas that is later used to upload the files. |
− | To transfer the downloaded and renamed files onto the iGEM server the selenium browser automation tool (<a class="urlextern" href="http://www.seleniumhq.org/" rel="nofollow" target="_Blank" title="http://www.seleniumhq.org/">http://www.seleniumhq.org/</a>) in combination with the sideflow-plugin (<a class="urlextern" href="https://github.com/73rhodes/sideflow" rel="nofollow" target="_Blank" title="https://github.com/73rhodes/sideflow">https://github.com/73rhodes/sideflow</a>) was used. This plugin allows for the creation of loops in selenium routines as needed to upload a list of files. The steps in the selenium routine are basically the coded equivalent to clicking on the upload-button, selecting a file, clicking the <code>ignore any warnings</code> button and submit the form. | + | To transfer the downloaded and renamed files onto the iGEM server the selenium browser automation tool (<a class="urlextern" href="http://www.seleniumhq.org/" rel="nofollow" target="_Blank" title="http://www.seleniumhq.org/">http://www.seleniumhq.org/</a>) in combination with the sideflow-plugin (<a class="urlextern" href="https://github.com/73rhodes/sideflow" rel="nofollow" target="_Blank" title="https://github.com/73rhodes/sideflow">https://github.com/73rhodes/sideflow</a>) was used. This plugin allows for the creation of loops in selenium routines as needed to upload a list of files. The steps in the selenium routine are basically the coded equivalent to clicking on the upload-button, selecting a file, clicking the ignore any warnings button and submit the form. |
− | Once the files are on the server, it is necessary to get their location on this server to use it as image source in the html-text on the sites. As the iGEM Server assigns a variable path to every file (as <code>/e/e4/filename.png</code>) this path has to be probed for every single image to be used on the site. | + | Once the files are on the server, it is necessary to get their location on this server to use it as image source in the html-text on the sites. As the iGEM Server assigns a variable path to every file (as /e/e4/filename.png) this path has to be probed for every single image to be used on the site. |
| This routine is implemented in the wikitranslate script together with a routine for replacing the linked headers by unlinked ones and one for adding preceding and following text, such that, for example custom styling for a specific page can be done directly in the protocol. The script is used on a web server basis (as cgi - script) and thus can be used by every team member with no needs for programming skills. It then returns an html-file that contains the source code to paste in the manually created new external wiki page. | | This routine is implemented in the wikitranslate script together with a routine for replacing the linked headers by unlinked ones and one for adding preceding and following text, such that, for example custom styling for a specific page can be done directly in the protocol. The script is used on a web server basis (as cgi - script) and thus can be used by every team member with no needs for programming skills. It then returns an html-file that contains the source code to paste in the manually created new external wiki page. |
| </p> | | </p> |
Line 78: |
Line 95: |
| <!-- EDIT4 SECTION "Refining the external Wiki" [3549-4226] --> | | <!-- EDIT4 SECTION "Refining the external Wiki" [3549-4226] --> |
| <h2 class="sectionedit5">Script-Code</h2> | | <h2 class="sectionedit5">Script-Code</h2> |
| + | |
| + | |
| + | |
| + | <div class="accordion"> |
| + | <div class="accordion-section"> |
| + | <a class="accordion-section-title" href="#accordion-1">dwfileload.py:</a> |
| + | <div id="accordion-1" class="accordion-section-content" style="display:none; padding:15px;"> |
| + | <div class="table sectionedit6"> |
| + | |
| + | #!/usr/bin/env python<br/><br/> from bs4 import BeautifulSoup # parse raw html and extract elements<br/> import requests<br/> from requests.auth import HTTPBasicAuth<br/> import cgi<br/> import urllib3<br/><br/> ##################<br/> # DEFINITIONS<br/> ##################<br/><br/> def getdwsource(dwsite):<br/> # get the subsite of the internal wiki specified as site<br/> # use the dummy usre to provide access to the wiki<br/> dokuwiki=requests.get('https://wiki.uni-freiburg.de/igem2015/doku.php?id=%s&do=export_xhtmlbody'%dwsite, auth=HTTPBasicAuth('dummy', 'igem2015'))<br/> # extract the html of the requests-object by using beautifulsoup<br/> soup=BeautifulSoup(dokuwiki.text, 'html.parser')<br/> # print('https://wiki.uni-freiburg.de/igem2015/doku.php?id=%s&do=export_xhtmlbody'%site)<br/> return soup<br/> <br/> def getdwpicnames(source):<br/> ########<br/> # returns a dict of the the names of all images in the source code as keys with the corresponding links to the image and the info-page<br/> ########<br/> picnamesdic={}<br/> for img in source.find_all('img'):<br/> # extract the name of all pictures from the src-string<br/> try:<br/> # get the name of the picnamesdic<br/> dwname=img.get('src').split('media=')[1]<br/> # use the name as key for a dict to store the links for src and href<br/> picnamesdic[dwname]=[img.get('src')]<br/> picnamesdic[dwname].append(img.parent.get('href'))<br/> print('+ \t %s '%img.get('src').split('media=')[1])<br/> # print('dwlink=%s'%picnamesdic[dwname])<br/> except:<br/> print('- \t\t %s '%img.get('src').split('/')[-1])<br/> return picnamesdic<br/><br/> def unescape(s):<br/> s = s.replace("&lt;", "<")<br/> s = s.replace("&gt;", ">")<br/> s = s.replace("%3A", ":")<br/> # this has to be last:<br/> s = s.replace("&amp;", "&")<br/> return s<br/><br/> def getpicurl(picname):<br/> <br/> # input: The name of a file uploaded on the iGEM 2015 Wiki-Server #<br/> # IMPOARTNT: The picture has to be uploaded before running the script! #<br/> # picname=input('please paste the name of an uploaded iGEM-wiki file:\n')<br/> <br/> # correct picname for changes the iGEM-Server needs<br/> picname=picname.replace(':','-' )<br/> <br/> # define fix url for Wiki-Sever #<br/> url='https://2015.igem.org/File:Freiburg_%s'%picname<br/> print('the url I looked for was:\n%s' %url)<br/> <br/> # get raw_html from url as specified here: http://stackoverflow.com/questions/17257912/how-to-print-raw-html-string-using-urllib3 #<br/> <br/> http_pool = urllib3.connection_from_url(url)<br/> r = http_pool.urlopen('GET',url)<br/> raw_html=r.data.decode('utf-8')<br/> <br/> # initialise bs-object '<br/> soup = BeautifulSoup(raw_html, 'html.parser')<br/> <br/> # find the href-link in an a-object in a div with id=file #<br/> try:<br/> serverlink='https://2015.igem.org'+soup.find(id='file').find('a').get('href')<br/> # return the link #<br/> return '%(x)s, %(y)s' % {'x': picname, 'y' : serverlink}<br/> except:<br/> return None<br/><br/><br/> ###################<br/> # BEGIN PROGRAMME<br/> ###################<br/><br/> # ask for internal wiki site to read<br/> dwsite=input('dwsite (in please in quotations):\t')<br/> # get sourcecode<br/> dwsource=getdwsource(dwsite)<br/> # get all picture names within a href<br/> picnamesdic=getdwpicnames(dwsource)<br/> # initialize list of urls to download<br/> urldic = {}<br/> # fill the list<br/> for key in picnamesdic:<br/> if getpicurl(key) == None:<br/> urldic[key]='/igem2015/lib/exe/fetch.php?media='+key<br/><br/> # download the images in the current directory (replace non usable syntax and append Freiburg_)<br/> for url in urldic:<br/> r=requests.get('http://wiki.uni-freiburg.de'+urldic[url], auth=HTTPBasicAuth('dummy', 'igem2015'), stream=True)<br/> if r.status_code == 200:<br/> f = open('Freiburg_'+url.replace(':','-'), 'wb')<br/> f.write(r.content)<br/> f.close()<br/><br/> f = open('files.txt', 'a')<br/> for key in urldic.keys():<br/> f.write('"{}"'.format(key.replace(':','-')))<br/> f.write(', ')<br/> f.close()<br/> |
| + | |
| + | |
| + | </div> |
| + | </div><!-- end accordion-section-content --> |
| + | </div><!-- end accordion-section --> |
| + | </div> <!-- end accordion --> |
| + | |
| + | |
| + | <p> |
| + | </p> |
| + | |
| + | |
| + | <div class="accordion"> |
| + | <div class="accordion-section"> |
| + | <a class="accordion-section-title" href="#accordion-2">Presets for Selenium IDE</a> |
| + | <div id="accordion-2" class="accordion-section-content" style="display:none; padding:15px;"> |
| + | <div class="table sectionedit6"> |
| + | |
| + | <table summary="fileupload" id="fileupload" > |
| + | <tr> |
| + | <th>command</th> |
| + | <th>target</th> |
| + | <th>value</th> |
| + | </tr> |
| + | <tr> |
| + | <td>storeEval</td> |
| + | <td>new Array(YOURARRAY);</td> |
| + | <td>myarray</td> |
| + | </tr> |
| + | <tr> |
| + | <td>getEval</td> |
| + | <td>index=0;</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>while</td> |
| + | <td>index < storedVars['myarray'].length</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>open</td> |
| + | <td>/Special:Upload</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>waitForPageToLoad</td> |
| + | <td></td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>storeEval</td> |
| + | <td>index</td> |
| + | <td>temp</td> |
| + | </tr> |
| + | <tr> |
| + | <td>echo</td> |
| + | <td>javascript{'/home/USER/CITY_'+storedVars['myarray'][storedVars['temp']]}</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>type</td> |
| + | <td>id=wpUploadFile</td> |
| + | <td>javascript{'/home/USER/CITY_'+storedVars['myarray'][storedVars['temp']]}</td> |
| + | </tr> |
| + | <tr> |
| + | <td>click</td> |
| + | <td>id=wpIgnoreWarning</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>clickAndWait</td> |
| + | <td>name=wpUpload</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>getEval</td> |
| + | <td>index++;</td> |
| + | <td></td> |
| + | </tr> |
| + | <tr> |
| + | <td>endWhile</td> |
| + | <td></td> |
| + | <td></td> |
| + | </tr> |
| + | </table> |
| + | |
| + | </div> |
| + | </div><!-- end accordion-section-content --> |
| + | </div><!-- end accordion-section --> |
| + | </div> <!-- end accordion --> |
| + | |
| + | <p> |
| + | </p> |
| + | |
| + | <div class="accordion"> |
| + | <div class="accordion-section"> |
| + | <a class="accordion-section-title" href="#accordion-3">HTML for Webserver-Input</a> |
| + | <div id="accordion-3" class="accordion-section-content" style="display:none; padding:15px;"> |
| + | <div class="table sectionedit6"> |
| + | |
| + | |
| + | |
| + | <form method="get" action="wikitranslate.py"><br/><br/> <div><br/> <h3>Choose option:</h3><br/> <input type="checkbox" name="rmheaderlink" value="True"> Remove headerlinks<br><br/> <input type="checkbox" name="changepicurl" value="True"> Change the picture links<br><br/> <input type="checkbox" name="appendtextbefore" value="True"> Add preceding text <br><br/> <input type="checkbox" name="appendtextafter" value="True"> Append text <br><br/> <input type="checkbox" name="changeprotocols" value="True"> Change protocol links <br><br/> <input type="checkbox" name="changeregistry" value="True"> Link registry entries <br><br/> </div><br/> <h3>Provide information if necessary</h3><br/> <div><br/> <label for="dwhtml">Name of an internal wiki-page (e.g. <em>labjournal:cellfree</em>):</label><br/> <input id="dwhtml" name="dwsite" required></textarea><br/> </div><br/> <div><br/> <label for="textbefore">Text to precede</label><br/> <textarea id='textbefore' cols="20" rows="5" name="textbefore"><br/> {{Freiburg/CSS}}<br/> <html><br/> </textarea> <br/> </div><br/> <div><br/> <label for="textafter">Text to append</label><br/> <textarea id="textafter" cols="20" rows="5" name="textafter"><br/> </html><br/> </div> <br/> <div><br/> <input type="submit" value="Submit"><br/> </div><br/><br/> </form> |
| + | |
| + | |
| + | |
| + | </div> |
| + | </div><!-- end accordion-section-content --> |
| + | </div><!-- end accordion-section --> |
| + | </div> <!-- end accordion --> |
| + | |
| + | <p> |
| + | </p> |
| + | |
| + | <div class="accordion"> |
| + | <div class="accordion-section"> |
| + | <a class="accordion-section-title" href="#accordion-4">CGI script code to be run on Webserver</a> |
| + | <div id="accordion-4" class="accordion-section-content" style="display:none; padding:15px;"> |
| + | <div class="table sectionedit6"> |
| + | |
| + | #!/usr/bin/python<br/> import cgi<br/> form = cgi.FieldStorage() # instantiate only once!<br/><br/> dwsite = form.getfirst('dwsite', 'empty')<br/> #dwsite = 'labjournal:ilab'<br/><br/> isrmheaderlinks=form.getfirst('rmheaderlink', False)<br/> ischangepicurl=form.getfirst('changepicurl', False)<br/> appendtextbefore=form.getfirst('appendtextbefore', False)<br/> appendtextafter=form.getfirst('appendtextafter', False)<br/> ischangeprotocols=form.getfirst('changeprotocols', False)<br/> isregistry=form.getfirst('changeregistry', False)<br/><br/> #isrmheaderlinks=True<br/> #ischangepicurl=True<br/> #appendtextbefore=True<br/> #appendtextafter=True<br/> #ischangeprotocols=True<br/> #isregistry=True<br/><br/> #textbefore='some text before'<br/> #textafter='some text after'<br/><br/> textbefore=form.getfirst('textbefore', '')<br/> textafter=form.getfirst('textafter', '')<br/><br/> # Avoid script injection escaping the user input<br/> dwsite = cgi.escape(dwsite)<br/><br/> from bs4 import BeautifulSoup # parse raw html and extract elements<br/> import urllib3 # read html-text from url<br/> import requests<br/> from requests.auth import HTTPBasicAuth<br/> import sys<br/> from cStringIO import StringIO<br/> import csv<br/> #import html <br/><br/> old_stdout = sys.stdout<br/> sys.stdout = mystdout = StringIO()<br/><br/> # read in the dict with internal and external protocol names<br/> protocolsdic = {}<br/> reader = csv.DictReader(open('protocols_names.csv', 'r'))<br/> for row in reader:<br/> protocolsdic[row['internal']]=row['external']<br/> <br/> # remove empty dict entries<br/> protocolsdic.pop('', None)<br/><br/> # read in the dict with registry entries to replace<br/> registrydic = {}<br/> reader = csv.DictReader(open('registry_links.csv', 'r'))<br/> for row in reader:<br/> registrydic[row['internal']]='<a href=%(x)s>%(y)s</a>' % {'x' : '"{}"'.format(row['external']), 'y' : row['internal']}<br/><br/> ################<br/> # BEGIN DEFINITIONS <br/> ################<br/><br/> def getdwsource(dwsite):<br/> # get the subsite of the internal wiki specified as site<br/> # use the dummy usre to provide access to the wiki<br/> dokuwiki=requests.get('https://wiki.uni-freiburg.de/igem2015/doku.php?id=%s&do=export_xhtmlbody'%dwsite, auth=HTTPBasicAuth('dummy', 'igem2015'))<br/> # extract the html of the requests-object by using beautifulsoup<br/> soup=BeautifulSoup(dokuwiki.text, 'html.parser')<br/> # print('https://wiki.uni-freiburg.de/igem2015/doku.php?id=%s&do=export_xhtmlbody'%site)<br/> return soup<br/><br/> def sfah(source):<br/> # return the soup-objects of all headers<br/> return source.findAll('h1') + source.findAll('h2') + source.findAll('h3') + source.findAll('h4') + source.findAll('h5') + source.findAll('h6')<br/><br/> def rmheaderlinks(soup):<br/> ########<br/> # removes the a-href links from the headers of an internal dw-file<br/> ########<br/> rmheaderlinksdic={}<br/> for header in sfah(soup):<br/> rmheaderlinksdic[unicode(header.a)]=header.a.text<br/> return rmheaderlinksdic<br/><br/> def getdwpicnames(source):<br/> ########<br/> # returns a dict of the the names of all images in the source code as keys with the corresponding links to the image and the info-page<br/> ########<br/> picnamesdic={}<br/> for img in source.find_all('img'):<br/> # extract the name of all pictures from the src-string<br/> try:<br/> # get the name of the picnamesdic<br/> dwname=img.get('src').split('media=')[1]<br/> # use the name as key for a dict to store the links for src and href<br/> picnamesdic[dwname]=[img.get('src')]<br/> picnamesdic[dwname].append(img.parent.get('href'))<br/> print('+ \t %s '%img.get('src').split('media=')[1])<br/> # print('dwlink=%s'%picnamesdic[dwname])<br/> except:<br/> print('- \t\t %s '%img.get('src').split('/')[-1])<br/> return picnamesdic<br/> <br/> def getpicurl(picname):<br/> # input: The name of a file uploaded on the iGEM 2015 Wiki-Server #<br/> # IMPORTANT: The picture has to be uploaded before running the script! #<br/> # picname=input('please paste the name of an uploaded iGEM-wiki file:\n')<br/> <br/> # correct picname for changes the iGEM-Server needs<br/> picname=picname.replace(':','-' )<br/> <br/> # define fix url for Wiki-Sever #<br/> url='https://2015.igem.org/File:Freiburg_%s'%picname<br/> #print('the url I looked for was:\n%s' %url)<br/> <br/> # get raw_html from url as specified here: http://stackoverflow.com/questions/17257912/how-to-print-raw-html-string-using-urllib3 #<br/> <br/> http_pool = urllib3.connection_from_url(url)<br/> r = http_pool.urlopen('GET',url)<br/> raw_html=r.data.decode('utf-8')<br/> <br/> # initialise bs-object '<br/> soup = BeautifulSoup(raw_html, 'html.parser')<br/> <br/> # find the href-link in an a-object in a div with id=file #<br/> try:<br/> serverlink='https://2015.igem.org'+soup.find(id='file').find('a').get('href')<br/> # return the link #<br/> return serverlink<br/> except:<br/> return None<br/> <br/> def unescape(s):<br/> s = s.replace("&lt;", "<")<br/> s = s.replace("&gt;", ">")<br/> # this has to be last:<br/> s = s.replace("&amp;", "&")<br/> return s<br/><br/> def changeprotocols(soup):<br/> returndic = {}<br/> for link in soup.findAll('a'):<br/> linksource = link.get('href')<br/> for name in protocolsdic:<br/> if linksource != None:<br/> if unicode(name) in linksource :<br/> print(unicode(name))<br/> # generate pairs for replacement using the absolute path for the iGEM server protocols section<br/> returndic[linksource] =unicode('https://2015.igem.org/Team:Freiburg/Protocols/')+protocolsdic[name]<br/> return returndic<br/><br/><br/> ##########################<br/> # BEGIN PROGRAMME<br/> ##########################<br/><br/> # set the subprogramcounter<br/> prog_count = 0<br/><br/> # get the source code<br/> dwsource=getdwsource(dwsite)<br/><br/> # convert it to replaceable text<br/> exthtml=unicode(dwsource)<br/><br/> # initialize dic to replace elements<br/> rpdic={}<br/><br/> ### is rmheaderlinks ###<br/> if isrmheaderlinks:<br/> # compute dic to replace headerlinks<br/> rpdic.update(rmheaderlinks(dwsource))<br/> prog_count+=1<br/><br/> ### is changeprotocols ###<br/> if ischangeprotocols:<br/> rpdic.update(changeprotocols(dwsource))<br/> prog_count+=1<br/><br/> ### is changepicurl ###<br/> missingimage = False<br/><br/> if ischangepicurl:<br/> picnamesdic=getdwpicnames(dwsource)<br/> for key in picnamesdic:<br/> serverlink=getpicurl(key)<br/> if serverlink != None:<br/> rpdic.update({cgi.escape(picnamesdic[key][0]):serverlink})<br/> else:<br/> missingimage = True<br/> if picnamesdic[key][1]:<br/> rpdic.update({cgi.escape(picnamesdic[key][1]):serverlink})<br/> prog_count+=1<br/><br/> ### is registry ###<br/> if isregistry:<br/> rpdic.update(registrydic)<br/> prog_count+=1<br/> <br/> ### cancel output if no program was called ###<br/> if prog_count == 0:<br/> sys.exit(0)<br/><br/> ### replacing ###<br/> exthtmlold = exthtml<br/> for text in rpdic.keys():<br/> # exthtml = exthtml.replace(cgi.escape(text),unescape(rpdic[text]))<br/> exthtml = exthtml.replace(text,rpdic[text])<br/><br/> sys.stdout=old_stdout<br/><br/> if not missingimage:<br/><br/> print "Content-Disposition: attachment; filename=\"%s.html\"\r\n\n"%dwsite<br/><br/> if appendtextbefore:<br/> print(textbefore.encode('utf8'))<br/> print(exthtml.encode('utf8'))<br/> if appendtextafter:<br/> print(textafter.encode('utf8'))<br/> else:<br/> print "Content-type: text/html \n"<br/><br/> print('There is an image missing!!') |
| + | |
| + | </div> |
| + | </div><!-- end accordion-section-content --> |
| + | </div><!-- end accordion-section --> |
| + | </div> <!-- end accordion --> |
| + | |
| + | |
| + | |
| <div class="tags"> | | <div class="tags"> |
| <span> | | <span> |