import bs4 import uuid from functools import partial from six import text_type # Use the default python parser as this is lenient and does not # wrap content in extra tags PYBLOQS_ID_PREFIX = "pybloqs_id_" DEFAULT_PARSER = "html.parser" def parse(string): """ Parses the string into a beautiful soup tree. :param string: String to parse. :return: Soup. """ return bs4.BeautifulSoup(string, DEFAULT_PARSER) def root(tag_name="html", doctype=None, **kwargs): """ Creates a new soup with the given root element. :param tag_name: Root element tag name. :param doctype: Optional doctype tag to add. :param kwargs: Optional parameters passed down to soup.new_tag() :return: Soup. """ soup = parse("") if doctype is not None: soup.append(bs4.Doctype(doctype)) tag = soup.new_tag(tag_name, **kwargs) tag.soup = soup soup.append(tag) return tag def render(item, pretty=True, encoding="utf8"): """ Renders the given element into a string. :param item: Item to render. Must be an element or soup instance. :param pretty: Toggles pretty formatting of the resulting string. :return: Rendered content. """ return item.prettify(encoding=encoding).decode('utf8') if pretty else text_type(item) def append_to(parent, tag, **kwargs): """ Append an element to the supplied parent. :param parent: Parent to append to. :param tag: Tag to create. :param kwargs: Tag kwargs. :return: New element. """ if hasattr(parent, "soup"): soup = parent.soup else: soup = parent.find_parent("html") # Create Tag explicitly instead of using new_tag, otherwise attribute "name" leads to clash with tag-name in bs4 new_tag = bs4.Tag(builder=soup.builder, name=tag, attrs=kwargs) new_tag.soup = soup parent.append(new_tag) return new_tag def construct_element(container=None, content=None, tag=None, element_type=None): """ Constructs an element and appends it to the container. :param container: Container to add the element to. :param content: String representation of content (e.g. JS or CSS) :param tag: Tag name, e.g. "script" or "style" :param element_type: E.g. "text/javascript" or "text/css" :return: New element. """ if container is None: el = root(tag, type=element_type) else: el = append_to(container, tag, type=element_type) if content is not None: el.string = content return el js_elem = partial(construct_element, tag='script', element_type='text/javascript') css_elem = partial(construct_element, tag='style', element_type='text/css') def set_id_generator(generator): """ Sets the global id generator function (must be a python generator function) :param generator: Generator function to use. """ global _id_generator _id_generator = generator def id_generator_uuid(): """ Generates unique identifiers using the `uuid` package. """ while True: yield str(uuid.uuid4()).replace("-", "") def id_generator_sequential(): """ Generatres unique identifiers sequentially from a known constant seed. """ counter = 0 while True: yield PYBLOQS_ID_PREFIX + str(counter) counter += 1 def id_generator(): return _id_generator() _id_generator = id_generator_sequential