Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating a recursive sitemap with relative href links

Tags:

python

jinja2

I'm using Flask to expose a local directory of HTML files on a web page.

I am also using a jinja2 to generate the sitemap in the lefthand div of my main endpoint.

I am unable to correctly specify the URL to the endpoint of my subfolders.

As mentioned in the code below, how would I dynamically build a relative link from /docs (i.e. /docs/folder1/subfolder1/SubFolder1Page.html)? The way I am currently setting the value for href obviously does not work.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Docs Demo</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div id="container">
        <div class="left_frame">
            <h1>{{ tree.name }}</h1>
            <ul>
            {%- for item in tree.children recursive %}
                <!-- How would I build a relative link from /docs/ i.e. /docs/folder1/subfolder1/SubFolder1Page.html -->
                <li><a href="docs/{{ item.name }}" target="iframe1">{{ item.name }}
                {%- if item.children -%}
                    <ul>{{ loop(item.children) }}</ul>
                {%- endif %}</a></li>
            {%- endfor %}
            </ul>
        </div>
        <div class="right_frame">
            <iframe name="iframe1"></iframe>
        </div>
    </div>
</body>
</html>

Folder structure example:

demo structure

How it looks overall displaying the contents of file1.html: sitemap view

like image 762
HEADLESS_0NE Avatar asked May 30 '17 20:05

HEADLESS_0NE


2 Answers

So I figured out a satisfying way of solving my own issue.

I managed to get this very functional result: Flask serving Docs demo

Do note that my template is only good for files with .html extension, though it can be easily enhanced to support other file extensions.

Here is my finalized templates\template.html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Docs Demo</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div id="container">
        <div class="left_frame">
            <h1>{{ tree.name }}</h1>
            <ul>
            {%- for item in tree.children recursive %}
                {% if '.html' in item.name %}
                    <li><a href="docs/{{ item.name }}" target="iframe1">
                    {{ item.name.split('/')[-1:][0] }}
                    {%- if item.children -%}
                        <ul>{{ loop(item.children) }}</ul>
                    {%- endif %}</a></li>
                {% else %}
                    <li>{{ item.name }}
                    {%- if item.children -%}
                        <ul>{{ loop(item.children) }}</ul>
                    {%- endif %}</li>
                {% endif %}
            {%- endfor %}
            </ul>
        </div>
        <div class="right_frame">
            <iframe name="iframe1"></iframe>
        </div>
    </div>
</body>
</html>

You can refer to King Reload's answer for an analysis of what I've changed in the template.html file to make this work correctly.

And here is the demo_app.py script that serves my document HTML files via Flask:

import threading
import os
import webbrowser
from flask import Flask, render_template, send_from_directory


app = Flask(__name__, static_folder='static')

ROOT = os.path.dirname(os.path.abspath(__file__))
DOCS_ROOT = os.path.join(app.static_folder, 'docs')


@app.route('/')
def docs_tree():
    return render_template('template.html', tree=make_tree(DOCS_ROOT))


@app.route('/docs/<path:filename>')
def send_docs(filename):
    return send_from_directory(directory=DOCS_ROOT, 'docs'), filename=filename)


def make_tree(path):
    tree = dict(name=os.path.basename(path), children=[])
    try:
        lst = os.listdir(path)
    except OSError:
        pass  # ignore errors
    else:
        for name in lst:
            fn = os.path.join(path, name)
            if os.path.isdir(fn):
                tree['children'].append(make_tree(fn))
            else:
                np = os.path.join(path.replace(DOCS_ROOT, ''), name).replace('\\', '/')
                if np.startswith('/'):
                    np = np[1:]
                tree['children'].append(dict(name=np))
    return tree

if __name__ == '__main__':
    host = 'localhost'
    port = '8888'
    url = 'http://{h}:{p}'.format(h=host, p=port)
    threading.Timer(3, lambda: webbrowser.open(url)).start()
    app.run(host=host, port=port, debug=False)

Most notable changes in demo_app.py since asking my original question were the following:

  • After initializing app, I set DOCS_ROOT using app.static_folder;
  • In the function send_docs(), I changed the send_from_directory()'s directory argument to use DOCS_ROOT;
  • Inside of make_tree(), inside the else block of the for loop, I added:

    np = os.path.join(path.replace(DOCS_ROOT, ''), name).replace('\\', '/')
    if np.startswith('/'):
        np = np[1:]
    

    All this does is take the absolute path of name, remove what matches DOCS_ROOT, leaving only the relative path (and then replacing the \\ for /), resulting in a simple relative path from static/docs. If the relative path starts with a /, I remove it (since there is a trailing / from docs in template.html.


For anyone interested in the simplistic stylesheet (static\styles.css) I used (along with some updated enhancements):

html {
    min-height:100%;
    position:relative;
}

body {
    overflow:hidden;
}

.container {
    width:100%;
    overflow:auto;
}

.left_frame {
    float:left;
    background:#E8F1F5;
    width:25%;
    height:100vh;
}

.right_frame {
    float:right;
    background:#FAFAFA;
    width:75%;
    height:100vh;
}

.right_frame iframe {
    display:block;
    width:100%;
    height:100%;
    border:none;
}
like image 189
HEADLESS_0NE Avatar answered Nov 12 '22 18:11

HEADLESS_0NE


To add onto the solution of @HEADLESS_0NE:

He added a few more if statements in the for loop, like so:

       {%- for item in tree.children recursive %}
    ->        {% if '.html' in item.name %}
                <li><a href="docs/{{ item.name }}" target="iframe1">
    ->            {{ item.name.split('/')[-1:][0] }}
                {%- if item.children -%}
                    <ul>{{ loop(item.children) }}</ul>
                {%- endif %}</a></li>
    ->        {% else %}
    ->            <li>{{ item.name }}
    ->            {%- if item.children -%}
    ->                <ul>{{ loop(item.children) }}</ul>
    ->            {%- endif %}</li>
    ->        {% endif %}
        {%- endfor %}

Everything with an -> has been changed in the html, I couldn't find what he added in his python or css, but in short:

  • An if to check if there's a .html in the item.name.
  • A split on the item.name, so the / gets removed.
  • An else statement if there's no .html in the item.name.

This basically adds the ul's and the li's in the correct format.

For a more detailed explanation I hope HEADLESS_0NE could provide us of more information what he might've changed in the python script.

like image 3
King Reload Avatar answered Nov 12 '22 17:11

King Reload