1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals
3
4import re
5
6from django.forms.utils import flatatt
7from django.template import Variable, VariableDoesNotExist
8from django.template.base import FilterExpression, TemplateSyntaxError, kwarg_re
9from django.template.loader import get_template
10from django.utils import six
11from django.utils.encoding import force_str
12from django.utils.html import format_html
13from django.utils.safestring import mark_safe
14from django.utils.six.moves.urllib.parse import parse_qs, urlencode, urlparse, urlunparse
15
16from .text import text_value
17
18try:
19    from collections.abc import Mapping
20except ImportError:
21    # py27 fallback, remove if support for python 2.7 is dropped
22    from collections import Mapping
23
24
25# RegEx for quoted string
26QUOTED_STRING = re.compile(r'^["\'](?P<noquotes>.+)["\']$')
27
28
29def handle_var(value, context):
30    """
31    Handle template tag variable
32    """
33    # Resolve FilterExpression and Variable immediately
34    if isinstance(value, FilterExpression) or isinstance(value, Variable):
35        return value.resolve(context)
36    # Return quoted strings unquoted
37    # http://djangosnippets.org/snippets/886
38    stringval = QUOTED_STRING.search(value)
39    if stringval:
40        return stringval.group("noquotes")
41    # Resolve variable or return string value
42    try:
43        return Variable(value).resolve(context)
44    except VariableDoesNotExist:
45        return value
46
47
48def parse_token_contents(parser, token):
49    """
50    Parse template tag contents
51    """
52    bits = token.split_contents()
53    tag = bits.pop(0)
54    args = []
55    kwargs = {}
56    asvar = None
57    if len(bits) >= 2 and bits[-2] == "as":
58        asvar = bits[-1]
59        bits = bits[:-2]
60    if len(bits):
61        for bit in bits:
62            match = kwarg_re.match(bit)
63            if not match:
64                raise TemplateSyntaxError('Malformed arguments to tag "{}"'.format(tag))
65            name, value = match.groups()
66            if name:
67                kwargs[name] = parser.compile_filter(value)
68            else:
69                args.append(parser.compile_filter(value))
70    return {"tag": tag, "args": args, "kwargs": kwargs, "asvar": asvar}
71
72
73def split_css_classes(css_classes):
74    """
75    Turn string into a list of CSS classes
76    """
77    classes_list = text_value(css_classes).split(" ")
78    return [c for c in classes_list if c]
79
80
81def add_css_class(css_classes, css_class, prepend=False):
82    """
83    Add a CSS class to a string of CSS classes
84    """
85    classes_list = split_css_classes(css_classes)
86    classes_to_add = [c for c in split_css_classes(css_class) if c not in classes_list]
87    if prepend:
88        classes_list = classes_to_add + classes_list
89    else:
90        classes_list += classes_to_add
91    return " ".join(classes_list)
92
93
94def remove_css_class(css_classes, css_class):
95    """
96    Remove a CSS class from a string of CSS classes
97    """
98    remove = set(split_css_classes(css_class))
99    classes_list = [c for c in split_css_classes(css_classes) if c not in remove]
100    return " ".join(classes_list)
101
102
103def render_script_tag(url):
104    """
105    Build a script tag
106    """
107    url_dict = sanitize_url_dict(url)
108    url_dict.setdefault("src", url_dict.pop("url", None))
109    return render_tag("script", url_dict)
110
111
112def render_link_tag(url, rel="stylesheet", media=None):
113    """
114    Build a link tag
115    """
116    url_dict = sanitize_url_dict(url, url_attr="href")
117    url_dict.setdefault("href", url_dict.pop("url", None))
118    url_dict["rel"] = rel
119    if media:
120        url_dict["media"] = media
121    return render_tag("link", attrs=url_dict, close=False)
122
123
124def render_tag(tag, attrs=None, content=None, close=True):
125    """
126    Render a HTML tag
127    """
128    builder = "<{tag}{attrs}>{content}"
129    if content or close:
130        builder += "</{tag}>"
131    return format_html(
132        builder,
133        tag=tag,
134        attrs=mark_safe(flatatt(attrs)) if attrs else "",
135        content=text_value(content),
136    )
137
138
139def render_template_file(template, context=None):
140    """
141    Render a Template to unicode
142    """
143    assert isinstance(context, Mapping)
144    template = get_template(template)
145    return template.render(context)
146
147
148def url_replace_param(url, name, value):
149    """
150    Replace a GET parameter in an URL
151    """
152    url_components = urlparse(force_str(url))
153
154    params = parse_qs(url_components.query)
155
156    if value is None:
157        del params[name]
158    else:
159        params[name] = value
160
161    return mark_safe(
162        urlunparse(
163            [
164                url_components.scheme,
165                url_components.netloc,
166                url_components.path,
167                url_components.params,
168                urlencode(params, doseq=True),
169                url_components.fragment,
170            ]
171        )
172    )
173
174
175def sanitize_url_dict(url, url_attr="src"):
176    """
177    Sanitize url dict as used in django-bootstrap4 settings
178    """
179    if isinstance(url, six.string_types):
180        return {url_attr: url}
181    return url.copy()
182