opyml/opyml/outline.py

183 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
from defusedxml import ElementTree
from typing import List, Optional
from xml.etree import ElementTree as XmlBuilder
from .util import _dict_exclude_none
class Outline:
"""The OPML Outline element."""
def __init__(
self,
text: str,
type: Optional[str] = None,
is_comment: Optional[bool] = None,
is_breakpoint: Optional[bool] = None,
created: Optional[str] = None,
category: Optional[str] = None,
outlines: Optional[List["Outline"]] = None,
xml_url: Optional[str] = None,
description: Optional[str] = None,
html_url: Optional[str] = None,
language: Optional[str] = None,
title: Optional[str] = None,
version: Optional[str] = None,
url: Optional[str] = None,
) -> None:
"""Create a new Outline."""
self.text: str = text
"""Every outline element must have at least a text attribute, which is what
is displayed when an outliner opens the OPML document.
Version 1.0 OPML documents may omit this attribute, so for compatibility and
strictness this attribute is “technically optional” as it will be replaced
by an empty string if it is omitted.
Text attributes may contain encoded HTML markup."""
self.type: Optional[str] = type
"""A string that indicates how the other attributes of the Outline should be
interpreted."""
self.is_comment: Optional[bool] = is_comment
"""Indicating whether the outline is commented or not. By convention if an
outline is commented, all subordinate outlines are considered to also be
commented."""
self.is_breakpoint: Optional[bool] = is_breakpoint
"""Indicating whether a breakpoint is set on this outline. This attribute is
mainly necessary for outlines used to edit scripts."""
self.created: Optional[str] = created
"""The date-time (RFC822) that this Outline element was created."""
self.category: Optional[str] = category
"""A string of comma-separated slash-delimited category strings, in the
format defined by the [RSS 2.0 category][1] element. To represent a “tag”, the
category string should contain no slashes.
[1]: https://cyber.law.harvard.edu/rss/rss.html#ltcategorygtSubelementOfLtitemgt
"""
self.outlines: List["Outline"] = outlines if outlines is not None else []
"""Child Outline elements of the current one."""
self.xml_url: Optional[str] = xml_url
"""The HTTP address of the feed."""
self.description: Optional[str] = description
"""The top-level description element from the feed."""
self.html_url: Optional[str] = html_url
"""The top-level link element from the feed."""
self.language: Optional[str] = language
"""The top-level language element from the feed."""
self.title: Optional[str] = title
"""The top-level title element from the feed."""
self.version: Optional[str] = version
"""The version of the feeds format (such as RSS 0.91, 2.0, …)."""
self.url: Optional[str] = url
"""A link that can point to another OPML document or to something that can
be displayed in a web browser."""
@staticmethod
def from_element_tree(element: ElementTree) -> "Outline":
"""Parse an Outline object from an XML ElementTree."""
attr = element.attrib
outline = Outline(
text=attr.get("text", ""),
outlines=Outline.parse_outlines(element),
)
if "type" in attr:
outline.type = attr.get("type")
if "isComment" in attr and attr.get("isComment") in ["true", "false"]:
outline.is_comment = bool(attr.get("isComment"))
if "isBreakpoint" in attr and attr.get("isBreakpoint") in ["true", "false"]:
outline.is_comment = bool(attr.get("isBreakpoint"))
if "created" in attr:
outline.created = attr.get("created")
if "category" in attr:
outline.category = attr.get("category")
if "xmlUrl" in attr:
outline.xml_url = attr.get("xmlUrl")
if "description" in attr:
outline.description = attr.get("description")
if "htmlUrl" in attr:
outline.html_url = attr.get("htmlUrl")
if "language" in attr:
outline.language = attr.get("language")
if "title" in attr:
outline.title = attr.get("title")
if "version" in attr:
outline.version = attr.get("version")
if "url" in attr:
outline.url = attr.get("url")
return outline
@staticmethod
def parse_outlines(element: ElementTree) -> List["Outline"]:
"""Parse a list of Outline elements from an XML ElementTree."""
return list(
map(
lambda child: Outline.from_element_tree(child),
filter(lambda child: child.tag == "outline", element),
)
)
def insert_xml_element(self, parent: XmlBuilder.Element) -> None:
"""Insert the Outline into an XML Element."""
outline = XmlBuilder.SubElement(parent, "outline")
outline.attrib["text"] = self.text
if self.text is not None:
outline.attrib["text"] = self.text
if self.type is not None:
outline.attrib["type"] = self.type
if self.is_comment is not None:
outline.attrib["isComment"] = str(self.is_comment).lower()
if self.is_breakpoint is not None:
outline.attrib["isBreakpoint"] = str(self.is_breakpoint).lower()
if self.created is not None:
outline.attrib["created"] = self.created
if self.category is not None:
outline.attrib["category"] = self.category
if self.xml_url is not None:
outline.attrib["xmlUrl"] = self.xml_url
if self.description is not None:
outline.attrib["description"] = self.description
if self.html_url is not None:
outline.attrib["htmlUrl"] = self.html_url
if self.language is not None:
outline.attrib["language"] = self.language
if self.title is not None:
outline.attrib["title"] = self.title
if self.version is not None:
outline.attrib["version"] = self.version
if self.url is not None:
outline.attrib["url"] = self.url
for child_outline in self.outlines:
child_outline.insert_xml_element(outline)
def to_json(self) -> str:
"""Output the Outline as a JSON string."""
return json.dumps(
self,
default=lambda o: _dict_exclude_none(o.__dict__),
indent=2,
sort_keys=True,
)