183 lines
6.7 KiB
Python
183 lines
6.7 KiB
Python
|
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 feed’s 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,
|
|||
|
)
|