tags around some text and HTML-escape it. This is here since once twisted.web.html was deprecated it was hard to migrate the html.PRE from current code to twisted.web.template. For new code consider using twisted.web.template. @return: Escaped text wrapped in tags. @rtype: C{str} """ return f"{escape(text)}" def redirectTo(URL: bytes, request: IRequest) -> bytes: """ Generate a redirect to the given location. @param URL: A L{bytes} giving the location to which to redirect. @param request: The request object to use to generate the redirect. @type request: L{IRequest} provider @raise TypeError: If the type of C{URL} a L{str} instead of L{bytes}. @return: A L{bytes} containing HTML which tries to convince the client agent to visit the new location even if it doesn't respect the I{FOUND} response code. This is intended to be returned from a render method, eg:: def render_GET(self, request): return redirectTo(b"http://example.com/", request) """ if not isinstance(URL, bytes): raise TypeError("URL must be bytes") request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.redirect(URL) # FIXME: The URL should be HTML-escaped. # https://twistedmatrix.com/trac/ticket/9839 content = b""" click here """ % { b"url": escape(URL.decode("utf-8")).encode("utf-8") } return content class Redirect(resource.Resource): """ Resource that redirects to a specific URL. @ivar url: Redirect target URL to put in the I{Location} response header. @type url: L{bytes} """ isLeaf = True def __init__(self, url: bytes): super().__init__() self.url = url def render(self, request): return redirectTo(self.url, request) def getChild(self, name, request): return self class ParentRedirect(resource.Resource): """ Redirect to the nearest directory and strip any query string. This generates redirects like:: / \u2192 / /foo \u2192 / /foo?bar \u2192 / /foo/ \u2192 /foo/ /foo/bar \u2192 /foo/ /foo/bar?baz \u2192 /foo/ However, the generated I{Location} header contains an absolute URL rather than a path. The response is the same regardless of HTTP method. """ isLeaf = 1 def render(self, request: IRequest) -> bytes: """ Respond to all requests by redirecting to nearest directory. """ here = str(urlpath.URLPath.fromRequest(request).here()).encode("ascii") return redirectTo(here, request) class DeferredResource(resource.Resource): """ I wrap up a Deferred that will eventually result in a Resource object. """ isLeaf = 1 def __init__(self, d): resource.Resource.__init__(self) self.d = d def getChild(self, name, request): return self def render(self, request): self.d.addCallback(self._cbChild, request).addErrback(self._ebChild, request) from twisted.web.server import NOT_DONE_YET return NOT_DONE_YET def _cbChild(self, child, request): request.render(resource.getChildForRequest(child, request)) def _ebChild(self, reason, request): request.processingFailed(reason) class _SourceLineElement(Element): """ L{_SourceLineElement} is an L{IRenderable} which can render a single line of source code. @ivar number: A C{int} giving the line number of the source code to be rendered. @ivar source: A C{str} giving the source code to be rendered. """ def __init__(self, loader, number, source): Element.__init__(self, loader) self.number = number self.source = source @renderer def sourceLine(self, request, tag): """ Render the line of source as a child of C{tag}. """ return tag(self.source.replace(" ", " \N{NO-BREAK SPACE}")) @renderer def lineNumber(self, request, tag): """ Render the line number as a child of C{tag}. """ return tag(str(self.number)) class _SourceFragmentElement(Element): """ L{_SourceFragmentElement} is an L{IRenderable} which can render several lines of source code near the line number of a particular frame object. @ivar frame: A L{Failure}-style frame object for which to load a source line to render. This is really a tuple holding some information from a frame object. See L{Failure.frames} for specifics. """ def __init__(self, loader, frame): Element.__init__(self, loader) self.frame = frame def _getSourceLines(self): """ Find the source line references by C{self.frame} and yield, in source line order, it and the previous and following lines. @return: A generator which yields two-tuples. Each tuple gives a source line number and the contents of that source line. """ filename = self.frame[1] lineNumber = self.frame[2] for snipLineNumber in range(lineNumber - 1, lineNumber + 2): yield (snipLineNumber, linecache.getline(filename, snipLineNumber).rstrip()) @renderer def sourceLines(self, request, tag): """ Render the source line indicated by C{self.frame} and several surrounding lines. The active line will be given a I{class} of C{"snippetHighlightLine"}. Other lines will be given a I{class} of C{"snippetLine"}. """ for lineNumber, sourceLine in self._getSourceLines(): newTag = tag.clone() if lineNumber == self.frame[2]: cssClass = "snippetHighlightLine" else: cssClass = "snippetLine" loader = TagLoader(newTag(**{"class": cssClass})) yield _SourceLineElement(loader, lineNumber, sourceLine) class _FrameElement(Element): """ L{_FrameElement} is an L{IRenderable} which can render details about one frame from a L{Failure}. @ivar frame: A L{Failure}-style frame object for which to load a source line to render. This is really a tuple holding some information from a frame object. See L{Failure.frames} for specifics. """ def __init__(self, loader, frame): Element.__init__(self, loader) self.frame = frame @renderer def filename(self, request, tag): """ Render the name of the file this frame references as a child of C{tag}. """ return tag(self.frame[1]) @renderer def lineNumber(self, request, tag): """ Render the source line number this frame references as a child of C{tag}. """ return tag(str(self.frame[2])) @renderer def function(self, request, tag): """ Render the function name this frame references as a child of C{tag}. """ return tag(self.frame[0]) @renderer def source(self, request, tag): """ Render the source code surrounding the line this frame references, replacing C{tag}. """ return _SourceFragmentElement(TagLoader(tag), self.frame) class _StackElement(Element): """ L{_StackElement} renders an L{IRenderable} which can render a list of frames. """ def __init__(self, loader, stackFrames): Element.__init__(self, loader) self.stackFrames = stackFrames @renderer def frames(self, request, tag): """ Render the list of frames in this L{_StackElement}, replacing C{tag}. """ return [ _FrameElement(TagLoader(tag.clone()), frame) for frame in self.stackFrames ] class _NSContext: """ A mapping from XML namespaces onto their prefixes in the document. """ def __init__(self, parent: Optional["_NSContext"] = None): """ Pull out the parent's namespaces, if there's no parent then default to XML. """ self.parent = parent if parent is not None: self.nss: Dict[Optional[str], Optional[str]] = OrderedDict(parent.nss) else: self.nss = {"http://www.w3.org/XML/1998/namespace": "xml"} def get(self, k: Optional[str], d: Optional[str] = None) -> Optional[str]: """ Get a prefix for a namespace. @param d: The default prefix value. """ return self.nss.get(k, d) def __setitem__(self, k: Optional[str], v: Optional[str]) -> None: """ Proxy through to setting the prefix for the namespace. """ self.nss.__setitem__(k, v) def __getitem__(self, k: Optional[str]) -> Optional[str]: """ Proxy through to getting the prefix for the namespace. """ return self.nss.__getitem__(k) TEMPLATE_NAMESPACE = "http://twistedmatrix.com/ns/twisted.web.template/0.1" class _ToStan(handler.ContentHandler, handler.EntityResolver): """ A SAX parser which converts an XML document to the Twisted STAN Document Object Model. """ def __init__(self, sourceFilename: Optional[str]): """ @param sourceFilename: the filename the XML was loaded out of. """ self.sourceFilename = sourceFilename self.prefixMap = _NSContext() self.inCDATA = False def setDocumentLocator(self, locator: Locator) -> None: """ Set the document locator, which knows about line and character numbers. """ self.locator = locator def startDocument(self) -> None: """ Initialise the document. """ # Depending on our active context, the element type can be Tag, slot # or str. Since mypy doesn't understand that context, it would be # a pain to not use Any here. self.document: List[Any] = [] self.current = self.document self.stack: List[Any] = [] self.xmlnsAttrs: List[Tuple[str, str]] = [] def endDocument(self) -> None: """ Document ended. """ def processingInstruction(self, target: str, data: str) -> None: """ Processing instructions are ignored. """ def startPrefixMapping(self, prefix: Optional[str], uri: str) -> None: """ Set up the prefix mapping, which maps fully qualified namespace URIs onto namespace prefixes. This gets called before startElementNS whenever an C{xmlns} attribute is seen. """ self.prefixMap = _NSContext(self.prefixMap) self.prefixMap[uri] = prefix # Ignore the template namespace; we'll replace those during parsing. if uri == TEMPLATE_NAMESPACE: return # Add to a list that will be applied once we have the element. if prefix is None: self.xmlnsAttrs.append(("xmlns", uri)) else: self.xmlnsAttrs.append(("xmlns:%s" % prefix, uri)) def endPrefixMapping(self, prefix: Optional[str]) -> None: """ "Pops the stack" on the prefix mapping. Gets called after endElementNS. """ parent = self.prefixMap.parent assert parent is not None, "More prefix mapping ends than starts" self.prefixMap = parent def startElementNS( self, namespaceAndName: Tuple[str, str], qname: Optional[str], attrs: AttributesNSImpl, ) -> None: """ Gets called when we encounter a new xmlns attribute. @param namespaceAndName: a (namespace, name) tuple, where name determines which type of action to take, if the namespace matches L{TEMPLATE_NAMESPACE}. @param qname: ignored. @param attrs: attributes on the element being started. """ filename = self.sourceFilename lineNumber = self.locator.getLineNumber() columnNumber = self.locator.getColumnNumber() ns, name = namespaceAndName if ns == TEMPLATE_NAMESPACE: if name == "transparent": name = "" elif name == "slot": default: Optional[str] try: # Try to get the default value for the slot default = attrs[(None, "default")] except KeyError: # If there wasn't one, then use None to indicate no # default. default = None sl = slot( attrs[(None, "name")], default=default, filename=filename, lineNumber=lineNumber, columnNumber=columnNumber, ) self.stack.append(sl) self.current.append(sl) self.current = sl.children return render = None ordered = OrderedDict(attrs) for k, v in list(ordered.items()): attrNS, justTheName = k if attrNS != TEMPLATE_NAMESPACE: continue if justTheName == "render": render = v del ordered[k] # nonTemplateAttrs is a dictionary mapping attributes that are *not* in # TEMPLATE_NAMESPACE to their values. Those in TEMPLATE_NAMESPACE were # just removed from 'attrs' in the loop immediately above. The key in # nonTemplateAttrs is either simply the attribute name (if it was not # specified as having a namespace in the template) or prefix:name, # preserving the xml namespace prefix given in the document. nonTemplateAttrs = OrderedDict() for (attrNs, attrName), v in ordered.items(): nsPrefix = self.prefixMap.get(attrNs) if nsPrefix is None: attrKey = attrName else: attrKey = f"{nsPrefix}:{attrName}" nonTemplateAttrs[attrKey] = v if ns == TEMPLATE_NAMESPACE and name == "attr": if not self.stack: # TODO: define a better exception for this? raise AssertionError( f"<{{{TEMPLATE_NAMESPACE}}}attr> as top-level element" ) if "name" not in nonTemplateAttrs: # TODO: same here raise AssertionError( f"<{{{TEMPLATE_NAMESPACE}}}attr> requires a name attribute" ) el = Tag( "", render=render, filename=filename, lineNumber=lineNumber, columnNumber=columnNumber, ) self.stack[-1].attributes[nonTemplateAttrs["name"]] = el self.stack.append(el) self.current = el.children return # Apply any xmlns attributes if self.xmlnsAttrs: nonTemplateAttrs.update(OrderedDict(self.xmlnsAttrs)) self.xmlnsAttrs = [] # Add the prefix that was used in the parsed template for non-template # namespaces (which will not be consumed anyway). if ns != TEMPLATE_NAMESPACE and ns is not None: prefix = self.prefixMap[ns] if prefix is not None: name = f"{self.prefixMap[ns]}:{name}" el = Tag( name, attributes=OrderedDict( cast(Mapping[Union[bytes, str], str], nonTemplateAttrs) ), render=render, filename=filename, lineNumber=lineNumber, columnNumber=columnNumber, ) self.stack.append(el) self.current.append(el) self.current = el.children def characters(self, ch: str) -> None: """ Called when we receive some characters. CDATA characters get passed through as is. """ if self.inCDATA: self.stack[-1].append(ch) return self.current.append(ch) def endElementNS(self, name: Tuple[str, str], qname: Optional[str]) -> None: """ A namespace tag is closed. Pop the stack, if there's anything left in it, otherwise return to the document's namespace. """ self.stack.pop() if self.stack: self.current = self.stack[-1].children else: self.current = self.document def startDTD(self, name: str, publicId: str, systemId: str) -> None: """ DTDs are ignored. """ def endDTD(self, *args: object) -> None: """ DTDs are ignored. """ def startCDATA(self) -> None: """ We're starting to be in a CDATA element, make a note of this. """ self.inCDATA = True self.stack.append([]) def endCDATA(self) -> None: """ We're no longer in a CDATA element. Collect up the characters we've parsed and put them in a new CDATA object. """ self.inCDATA = False comment = "".join(self.stack.pop()) self.current.append(CDATA(comment)) def comment(self, content: str) -> None: """ Add an XML comment which we've encountered. """ self.current.append(Comment(content)) def _flatsaxParse(fl: Union[IO[AnyStr], str]) -> List["Flattenable"]: """ Perform a SAX parse of an XML document with the _ToStan class. @param fl: The XML document to be parsed. @return: a C{list} of Stan objects. """ parser = make_parser() parser.setFeature(handler.feature_validation, 0) parser.setFeature(handler.feature_namespaces, 1) parser.setFeature(handler.feature_external_ges, 0) parser.setFeature(handler.feature_external_pes, 0) s = _ToStan(getattr(fl, "name", None)) parser.setContentHandler(s) parser.setEntityResolver(s) parser.setProperty(handler.property_lexical_handler, s) parser.parse(fl) return s.document @implementer(ITemplateLoader) class XMLString: """ An L{ITemplateLoader} that loads and parses XML from a string. """ def __init__(self, s: Union[str, bytes]): """ Run the parser on a L{io.StringIO} copy of the string. @param s: The string from which to load the XML. @type s: L{str}, or a UTF-8 encoded L{bytes}. """ if not isinstance(s, str): s = s.decode("utf8") self._loadedTemplate: List["Flattenable"] = _flatsaxParse(io.StringIO(s)) """The loaded document.""" def load(self) -> List["Flattenable"]: """ Return the document. @return: the loaded document. """ return self._loadedTemplate class FailureElement(Element): """ L{FailureElement} is an L{IRenderable} which can render detailed information about a L{Failure}. @ivar failure: The L{Failure} instance which will be rendered. @since: 12.1 """ loader = XMLString( """ : : in : """ ) def __init__(self, failure, loader=None): Element.__init__(self, loader) self.failure = failure @renderer def type(self, request, tag): """ Render the exception type as a child of C{tag}. """ return tag(fullyQualifiedName(self.failure.type)) @renderer def value(self, request, tag): """ Render the exception value as a child of C{tag}. """ return tag(str(self.failure.value).encode("utf8")) @renderer def traceback(self, request, tag): """ Render all the frames in the wrapped L{Failure}'s traceback stack, replacing C{tag}. """ return _StackElement(TagLoader(tag), self.failure.frames) def formatFailure(myFailure): """ Construct an HTML representation of the given failure. Consider using L{FailureElement} instead. @type myFailure: L{Failure} @rtype: L{bytes} @return: A string containing the HTML representation of the given failure. """ result = [] flattenString(None, FailureElement(myFailure)).addBoth(result.append) if isinstance(result[0], bytes): # Ensure the result string is all ASCII, for compatibility with the # default encoding expected by browsers. return result[0].decode("utf-8").encode("ascii", "xmlcharrefreplace") result[0].raiseException() # Go read the definition of NOT_DONE_YET. For lulz. This is totally # equivalent. And this turns out to be necessary, because trying to import # NOT_DONE_YET in this module causes a circular import which we cannot escape # from. From which we cannot escape. Etc. glyph is okay with this solution for # now, and so am I, as long as this comment stays to explain to future # maintainers what it means. ~ C. # # See http://twistedmatrix.com/trac/ticket/5557 for progress on fixing this. NOT_DONE_YET = 1 _moduleLog = Logger() @implementer(ITemplateLoader) class TagLoader: """ An L{ITemplateLoader} that loads an existing flattenable object. """ def __init__(self, tag: "Flattenable"): """ @param tag: The object which will be loaded. """ self.tag: "Flattenable" = tag """The object which will be loaded.""" def load(self) -> List["Flattenable"]: return [self.tag] @implementer(ITemplateLoader) class XMLFile: """ An L{ITemplateLoader} that loads and parses XML from a file. """ def __init__(self, path: FilePath[Any]): """ Run the parser on a file. @param path: The file from which to load the XML. """ if not isinstance(path, FilePath): warnings.warn( # type: ignore[unreachable] "Passing filenames or file objects to XMLFile is deprecated " "since Twisted 12.1. Pass a FilePath instead.", category=DeprecationWarning, stacklevel=2, ) self._loadedTemplate: Optional[List["Flattenable"]] = None """The loaded document, or L{None}, if not loaded.""" self._path: FilePath[Any] = path """The file that is being loaded from.""" def _loadDoc(self) -> List["Flattenable"]: """ Read and parse the XML. @return: the loaded document. """ if not isinstance(self._path, FilePath): return _flatsaxParse(self._path) # type: ignore[unreachable] else: with self._path.open("r") as f: return _flatsaxParse(f) def __repr__(self) -> str: return f"" def load(self) -> List["Flattenable"]: """ Return the document, first loading it if necessary. @return: the loaded document. """ if self._loadedTemplate is None: self._loadedTemplate = self._loadDoc() return self._loadedTemplate # Last updated October 2011, using W3Schools as a reference. Link: # http://www.w3schools.com/html5/html5_reference.asp # Note that is explicitly omitted; its semantics do not work with # t.w.template and it is officially deprecated. VALID_HTML_TAG_NAMES = { "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datalist", "dd", "del", "details", "dfn", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "keygen", "kbd", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "tt", "u", "ul", "var", "video", "wbr", } class _TagFactory: """ A factory for L{Tag} objects; the implementation of the L{tags} object. This allows for the syntactic convenience of C{from twisted.web.template import tags; tags.a(href="linked-page.html")}, where 'a' can be basically any HTML tag. The class is not exposed publicly because you only ever need one of these, and we already made it for you. @see: L{tags} """ def __getattr__(self, tagName: str) -> Tag: if tagName == "transparent": return Tag("") # allow for E.del as E.del_ tagName = tagName.rstrip("_") if tagName not in VALID_HTML_TAG_NAMES: raise AttributeError(f"unknown tag {tagName!r}") return Tag(tagName) tags = _TagFactory() def renderElement( request: IRequest, element: IRenderable, doctype: Optional[bytes] = b"", _failElement: Optional[Callable[[Failure], "Element"]] = None, ) -> object: """ Render an element or other L{IRenderable}. @param request: The L{IRequest} being rendered to. @param element: An L{IRenderable} which will be rendered. @param doctype: A L{bytes} which will be written as the first line of the request, or L{None} to disable writing of a doctype. The argument should not include a trailing newline and will default to the HTML5 doctype C{''}. @returns: NOT_DONE_YET @since: 12.1 """ if doctype is not None: request.write(doctype) request.write(b"\n") if _failElement is None: _failElement = FailureElement d = flatten(request, element, request.write) def eb(failure: Failure) -> Optional[Deferred[None]]: _moduleLog.failure( "An error occurred while rendering the response.", failure=failure ) site = getattr(request, "site", None) if site is not None and site.displayTracebacks: assert _failElement is not None return flatten(request, _failElement(failure), request.write) else: request.write( b'An error occurred while rendering the response.' ) return None def finish(result: object, *, request: IRequest = request) -> object: request.finish() return result d.addErrback(eb) d.addBoth(finish) return NOT_DONE_YET
tags. @rtype: C{str} """ return f"{escape(text)}" def redirectTo(URL: bytes, request: IRequest) -> bytes: """ Generate a redirect to the given location. @param URL: A L{bytes} giving the location to which to redirect. @param request: The request object to use to generate the redirect. @type request: L{IRequest} provider @raise TypeError: If the type of C{URL} a L{str} instead of L{bytes}. @return: A L{bytes} containing HTML which tries to convince the client agent to visit the new location even if it doesn't respect the I{FOUND} response code. This is intended to be returned from a render method, eg:: def render_GET(self, request): return redirectTo(b"http://example.com/", request) """ if not isinstance(URL, bytes): raise TypeError("URL must be bytes") request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.redirect(URL) # FIXME: The URL should be HTML-escaped. # https://twistedmatrix.com/trac/ticket/9839 content = b""" click here """ % { b"url": escape(URL.decode("utf-8")).encode("utf-8") } return content class Redirect(resource.Resource): """ Resource that redirects to a specific URL. @ivar url: Redirect target URL to put in the I{Location} response header. @type url: L{bytes} """ isLeaf = True def __init__(self, url: bytes): super().__init__() self.url = url def render(self, request): return redirectTo(self.url, request) def getChild(self, name, request): return self class ParentRedirect(resource.Resource): """ Redirect to the nearest directory and strip any query string. This generates redirects like:: / \u2192 / /foo \u2192 / /foo?bar \u2192 / /foo/ \u2192 /foo/ /foo/bar \u2192 /foo/ /foo/bar?baz \u2192 /foo/ However, the generated I{Location} header contains an absolute URL rather than a path. The response is the same regardless of HTTP method. """ isLeaf = 1 def render(self, request: IRequest) -> bytes: """ Respond to all requests by redirecting to nearest directory. """ here = str(urlpath.URLPath.fromRequest(request).here()).encode("ascii") return redirectTo(here, request) class DeferredResource(resource.Resource): """ I wrap up a Deferred that will eventually result in a Resource object. """ isLeaf = 1 def __init__(self, d): resource.Resource.__init__(self) self.d = d def getChild(self, name, request): return self def render(self, request): self.d.addCallback(self._cbChild, request).addErrback(self._ebChild, request) from twisted.web.server import NOT_DONE_YET return NOT_DONE_YET def _cbChild(self, child, request): request.render(resource.getChildForRequest(child, request)) def _ebChild(self, reason, request): request.processingFailed(reason) class _SourceLineElement(Element): """ L{_SourceLineElement} is an L{IRenderable} which can render a single line of source code. @ivar number: A C{int} giving the line number of the source code to be rendered. @ivar source: A C{str} giving the source code to be rendered. """ def __init__(self, loader, number, source): Element.__init__(self, loader) self.number = number self.source = source @renderer def sourceLine(self, request, tag): """ Render the line of source as a child of C{tag}. """ return tag(self.source.replace(" ", " \N{NO-BREAK SPACE}")) @renderer def lineNumber(self, request, tag): """ Render the line number as a child of C{tag}. """ return tag(str(self.number)) class _SourceFragmentElement(Element): """ L{_SourceFragmentElement} is an L{IRenderable} which can render several lines of source code near the line number of a particular frame object. @ivar frame: A L{Failure}-style frame object for which to load a source line to render. This is really a tuple holding some information from a frame object. See L{Failure.frames} for specifics. """ def __init__(self, loader, frame): Element.__init__(self, loader) self.frame = frame def _getSourceLines(self): """ Find the source line references by C{self.frame} and yield, in source line order, it and the previous and following lines. @return: A generator which yields two-tuples. Each tuple gives a source line number and the contents of that source line. """ filename = self.frame[1] lineNumber = self.frame[2] for snipLineNumber in range(lineNumber - 1, lineNumber + 2): yield (snipLineNumber, linecache.getline(filename, snipLineNumber).rstrip()) @renderer def sourceLines(self, request, tag): """ Render the source line indicated by C{self.frame} and several surrounding lines. The active line will be given a I{class} of C{"snippetHighlightLine"}. Other lines will be given a I{class} of C{"snippetLine"}. """ for lineNumber, sourceLine in self._getSourceLines(): newTag = tag.clone() if lineNumber == self.frame[2]: cssClass = "snippetHighlightLine" else: cssClass = "snippetLine" loader = TagLoader(newTag(**{"class": cssClass})) yield _SourceLineElement(loader, lineNumber, sourceLine) class _FrameElement(Element): """ L{_FrameElement} is an L{IRenderable} which can render details about one frame from a L{Failure}. @ivar frame: A L{Failure}-style frame object for which to load a source line to render. This is really a tuple holding some information from a frame object. See L{Failure.frames} for specifics. """ def __init__(self, loader, frame): Element.__init__(self, loader) self.frame = frame @renderer def filename(self, request, tag): """ Render the name of the file this frame references as a child of C{tag}. """ return tag(self.frame[1]) @renderer def lineNumber(self, request, tag): """ Render the source line number this frame references as a child of C{tag}. """ return tag(str(self.frame[2])) @renderer def function(self, request, tag): """ Render the function name this frame references as a child of C{tag}. """ return tag(self.frame[0]) @renderer def source(self, request, tag): """ Render the source code surrounding the line this frame references, replacing C{tag}. """ return _SourceFragmentElement(TagLoader(tag), self.frame) class _StackElement(Element): """ L{_StackElement} renders an L{IRenderable} which can render a list of frames. """ def __init__(self, loader, stackFrames): Element.__init__(self, loader) self.stackFrames = stackFrames @renderer def frames(self, request, tag): """ Render the list of frames in this L{_StackElement}, replacing C{tag}. """ return [ _FrameElement(TagLoader(tag.clone()), frame) for frame in self.stackFrames ] class _NSContext: """ A mapping from XML namespaces onto their prefixes in the document. """ def __init__(self, parent: Optional["_NSContext"] = None): """ Pull out the parent's namespaces, if there's no parent then default to XML. """ self.parent = parent if parent is not None: self.nss: Dict[Optional[str], Optional[str]] = OrderedDict(parent.nss) else: self.nss = {"http://www.w3.org/XML/1998/namespace": "xml"} def get(self, k: Optional[str], d: Optional[str] = None) -> Optional[str]: """ Get a prefix for a namespace. @param d: The default prefix value. """ return self.nss.get(k, d) def __setitem__(self, k: Optional[str], v: Optional[str]) -> None: """ Proxy through to setting the prefix for the namespace. """ self.nss.__setitem__(k, v) def __getitem__(self, k: Optional[str]) -> Optional[str]: """ Proxy through to getting the prefix for the namespace. """ return self.nss.__getitem__(k) TEMPLATE_NAMESPACE = "http://twistedmatrix.com/ns/twisted.web.template/0.1" class _ToStan(handler.ContentHandler, handler.EntityResolver): """ A SAX parser which converts an XML document to the Twisted STAN Document Object Model. """ def __init__(self, sourceFilename: Optional[str]): """ @param sourceFilename: the filename the XML was loaded out of. """ self.sourceFilename = sourceFilename self.prefixMap = _NSContext() self.inCDATA = False def setDocumentLocator(self, locator: Locator) -> None: """ Set the document locator, which knows about line and character numbers. """ self.locator = locator def startDocument(self) -> None: """ Initialise the document. """ # Depending on our active context, the element type can be Tag, slot # or str. Since mypy doesn't understand that context, it would be # a pain to not use Any here. self.document: List[Any] = [] self.current = self.document self.stack: List[Any] = [] self.xmlnsAttrs: List[Tuple[str, str]] = [] def endDocument(self) -> None: """ Document ended. """ def processingInstruction(self, target: str, data: str) -> None: """ Processing instructions are ignored. """ def startPrefixMapping(self, prefix: Optional[str], uri: str) -> None: """ Set up the prefix mapping, which maps fully qualified namespace URIs onto namespace prefixes. This gets called before startElementNS whenever an C{xmlns} attribute is seen. """ self.prefixMap = _NSContext(self.prefixMap) self.prefixMap[uri] = prefix # Ignore the template namespace; we'll replace those during parsing. if uri == TEMPLATE_NAMESPACE: return # Add to a list that will be applied once we have the element. if prefix is None: self.xmlnsAttrs.append(("xmlns", uri)) else: self.xmlnsAttrs.append(("xmlns:%s" % prefix, uri)) def endPrefixMapping(self, prefix: Optional[str]) -> None: """ "Pops the stack" on the prefix mapping. Gets called after endElementNS. """ parent = self.prefixMap.parent assert parent is not None, "More prefix mapping ends than starts" self.prefixMap = parent def startElementNS( self, namespaceAndName: Tuple[str, str], qname: Optional[str], attrs: AttributesNSImpl, ) -> None: """ Gets called when we encounter a new xmlns attribute. @param namespaceAndName: a (namespace, name) tuple, where name determines which type of action to take, if the namespace matches L{TEMPLATE_NAMESPACE}. @param qname: ignored. @param attrs: attributes on the element being started. """ filename = self.sourceFilename lineNumber = self.locator.getLineNumber() columnNumber = self.locator.getColumnNumber() ns, name = namespaceAndName if ns == TEMPLATE_NAMESPACE: if name == "transparent": name = "" elif name == "slot": default: Optional[str] try: # Try to get the default value for the slot default = attrs[(None, "default")] except KeyError: # If there wasn't one, then use None to indicate no # default. default = None sl = slot( attrs[(None, "name")], default=default, filename=filename, lineNumber=lineNumber, columnNumber=columnNumber, ) self.stack.append(sl) self.current.append(sl) self.current = sl.children return render = None ordered = OrderedDict(attrs) for k, v in list(ordered.items()): attrNS, justTheName = k if attrNS != TEMPLATE_NAMESPACE: continue if justTheName == "render": render = v del ordered[k] # nonTemplateAttrs is a dictionary mapping attributes that are *not* in # TEMPLATE_NAMESPACE to their values. Those in TEMPLATE_NAMESPACE were # just removed from 'attrs' in the loop immediately above. The key in # nonTemplateAttrs is either simply the attribute name (if it was not # specified as having a namespace in the template) or prefix:name, # preserving the xml namespace prefix given in the document. nonTemplateAttrs = OrderedDict() for (attrNs, attrName), v in ordered.items(): nsPrefix = self.prefixMap.get(attrNs) if nsPrefix is None: attrKey = attrName else: attrKey = f"{nsPrefix}:{attrName}" nonTemplateAttrs[attrKey] = v if ns == TEMPLATE_NAMESPACE and name == "attr": if not self.stack: # TODO: define a better exception for this? raise AssertionError( f"<{{{TEMPLATE_NAMESPACE}}}attr> as top-level element" ) if "name" not in nonTemplateAttrs: # TODO: same here raise AssertionError( f"<{{{TEMPLATE_NAMESPACE}}}attr> requires a name attribute" ) el = Tag( "", render=render, filename=filename, lineNumber=lineNumber, columnNumber=columnNumber, ) self.stack[-1].attributes[nonTemplateAttrs["name"]] = el self.stack.append(el) self.current = el.children return # Apply any xmlns attributes if self.xmlnsAttrs: nonTemplateAttrs.update(OrderedDict(self.xmlnsAttrs)) self.xmlnsAttrs = [] # Add the prefix that was used in the parsed template for non-template # namespaces (which will not be consumed anyway). if ns != TEMPLATE_NAMESPACE and ns is not None: prefix = self.prefixMap[ns] if prefix is not None: name = f"{self.prefixMap[ns]}:{name}" el = Tag( name, attributes=OrderedDict( cast(Mapping[Union[bytes, str], str], nonTemplateAttrs) ), render=render, filename=filename, lineNumber=lineNumber, columnNumber=columnNumber, ) self.stack.append(el) self.current.append(el) self.current = el.children def characters(self, ch: str) -> None: """ Called when we receive some characters. CDATA characters get passed through as is. """ if self.inCDATA: self.stack[-1].append(ch) return self.current.append(ch) def endElementNS(self, name: Tuple[str, str], qname: Optional[str]) -> None: """ A namespace tag is closed. Pop the stack, if there's anything left in it, otherwise return to the document's namespace. """ self.stack.pop() if self.stack: self.current = self.stack[-1].children else: self.current = self.document def startDTD(self, name: str, publicId: str, systemId: str) -> None: """ DTDs are ignored. """ def endDTD(self, *args: object) -> None: """ DTDs are ignored. """ def startCDATA(self) -> None: """ We're starting to be in a CDATA element, make a note of this. """ self.inCDATA = True self.stack.append([]) def endCDATA(self) -> None: """ We're no longer in a CDATA element. Collect up the characters we've parsed and put them in a new CDATA object. """ self.inCDATA = False comment = "".join(self.stack.pop()) self.current.append(CDATA(comment)) def comment(self, content: str) -> None: """ Add an XML comment which we've encountered. """ self.current.append(Comment(content)) def _flatsaxParse(fl: Union[IO[AnyStr], str]) -> List["Flattenable"]: """ Perform a SAX parse of an XML document with the _ToStan class. @param fl: The XML document to be parsed. @return: a C{list} of Stan objects. """ parser = make_parser() parser.setFeature(handler.feature_validation, 0) parser.setFeature(handler.feature_namespaces, 1) parser.setFeature(handler.feature_external_ges, 0) parser.setFeature(handler.feature_external_pes, 0) s = _ToStan(getattr(fl, "name", None)) parser.setContentHandler(s) parser.setEntityResolver(s) parser.setProperty(handler.property_lexical_handler, s) parser.parse(fl) return s.document @implementer(ITemplateLoader) class XMLString: """ An L{ITemplateLoader} that loads and parses XML from a string. """ def __init__(self, s: Union[str, bytes]): """ Run the parser on a L{io.StringIO} copy of the string. @param s: The string from which to load the XML. @type s: L{str}, or a UTF-8 encoded L{bytes}. """ if not isinstance(s, str): s = s.decode("utf8") self._loadedTemplate: List["Flattenable"] = _flatsaxParse(io.StringIO(s)) """The loaded document.""" def load(self) -> List["Flattenable"]: """ Return the document. @return: the loaded document. """ return self._loadedTemplate class FailureElement(Element): """ L{FailureElement} is an L{IRenderable} which can render detailed information about a L{Failure}. @ivar failure: The L{Failure} instance which will be rendered. @since: 12.1 """ loader = XMLString( """ : : in : """ ) def __init__(self, failure, loader=None): Element.__init__(self, loader) self.failure = failure @renderer def type(self, request, tag): """ Render the exception type as a child of C{tag}. """ return tag(fullyQualifiedName(self.failure.type)) @renderer def value(self, request, tag): """ Render the exception value as a child of C{tag}. """ return tag(str(self.failure.value).encode("utf8")) @renderer def traceback(self, request, tag): """ Render all the frames in the wrapped L{Failure}'s traceback stack, replacing C{tag}. """ return _StackElement(TagLoader(tag), self.failure.frames) def formatFailure(myFailure): """ Construct an HTML representation of the given failure. Consider using L{FailureElement} instead. @type myFailure: L{Failure} @rtype: L{bytes} @return: A string containing the HTML representation of the given failure. """ result = [] flattenString(None, FailureElement(myFailure)).addBoth(result.append) if isinstance(result[0], bytes): # Ensure the result string is all ASCII, for compatibility with the # default encoding expected by browsers. return result[0].decode("utf-8").encode("ascii", "xmlcharrefreplace") result[0].raiseException() # Go read the definition of NOT_DONE_YET. For lulz. This is totally # equivalent. And this turns out to be necessary, because trying to import # NOT_DONE_YET in this module causes a circular import which we cannot escape # from. From which we cannot escape. Etc. glyph is okay with this solution for # now, and so am I, as long as this comment stays to explain to future # maintainers what it means. ~ C. # # See http://twistedmatrix.com/trac/ticket/5557 for progress on fixing this. NOT_DONE_YET = 1 _moduleLog = Logger() @implementer(ITemplateLoader) class TagLoader: """ An L{ITemplateLoader} that loads an existing flattenable object. """ def __init__(self, tag: "Flattenable"): """ @param tag: The object which will be loaded. """ self.tag: "Flattenable" = tag """The object which will be loaded.""" def load(self) -> List["Flattenable"]: return [self.tag] @implementer(ITemplateLoader) class XMLFile: """ An L{ITemplateLoader} that loads and parses XML from a file. """ def __init__(self, path: FilePath[Any]): """ Run the parser on a file. @param path: The file from which to load the XML. """ if not isinstance(path, FilePath): warnings.warn( # type: ignore[unreachable] "Passing filenames or file objects to XMLFile is deprecated " "since Twisted 12.1. Pass a FilePath instead.", category=DeprecationWarning, stacklevel=2, ) self._loadedTemplate: Optional[List["Flattenable"]] = None """The loaded document, or L{None}, if not loaded.""" self._path: FilePath[Any] = path """The file that is being loaded from.""" def _loadDoc(self) -> List["Flattenable"]: """ Read and parse the XML. @return: the loaded document. """ if not isinstance(self._path, FilePath): return _flatsaxParse(self._path) # type: ignore[unreachable] else: with self._path.open("r") as f: return _flatsaxParse(f) def __repr__(self) -> str: return f"" def load(self) -> List["Flattenable"]: """ Return the document, first loading it if necessary. @return: the loaded document. """ if self._loadedTemplate is None: self._loadedTemplate = self._loadDoc() return self._loadedTemplate # Last updated October 2011, using W3Schools as a reference. Link: # http://www.w3schools.com/html5/html5_reference.asp # Note that is explicitly omitted; its semantics do not work with # t.w.template and it is officially deprecated. VALID_HTML_TAG_NAMES = { "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datalist", "dd", "del", "details", "dfn", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "keygen", "kbd", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "tt", "u", "ul", "var", "video", "wbr", } class _TagFactory: """ A factory for L{Tag} objects; the implementation of the L{tags} object. This allows for the syntactic convenience of C{from twisted.web.template import tags; tags.a(href="linked-page.html")}, where 'a' can be basically any HTML tag. The class is not exposed publicly because you only ever need one of these, and we already made it for you. @see: L{tags} """ def __getattr__(self, tagName: str) -> Tag: if tagName == "transparent": return Tag("") # allow for E.del as E.del_ tagName = tagName.rstrip("_") if tagName not in VALID_HTML_TAG_NAMES: raise AttributeError(f"unknown tag {tagName!r}") return Tag(tagName) tags = _TagFactory() def renderElement( request: IRequest, element: IRenderable, doctype: Optional[bytes] = b"", _failElement: Optional[Callable[[Failure], "Element"]] = None, ) -> object: """ Render an element or other L{IRenderable}. @param request: The L{IRequest} being rendered to. @param element: An L{IRenderable} which will be rendered. @param doctype: A L{bytes} which will be written as the first line of the request, or L{None} to disable writing of a doctype. The argument should not include a trailing newline and will default to the HTML5 doctype C{''}. @returns: NOT_DONE_YET @since: 12.1 """ if doctype is not None: request.write(doctype) request.write(b"\n") if _failElement is None: _failElement = FailureElement d = flatten(request, element, request.write) def eb(failure: Failure) -> Optional[Deferred[None]]: _moduleLog.failure( "An error occurred while rendering the response.", failure=failure ) site = getattr(request, "site", None) if site is not None and site.displayTracebacks: assert _failElement is not None return flatten(request, _failElement(failure), request.write) else: request.write( b'An error occurred while rendering the response.' ) return None def finish(result: object, *, request: IRequest = request) -> object: request.finish() return result d.addErrback(eb) d.addBoth(finish) return NOT_DONE_YET
{escape(text)}