ping :) ----- Forwarded message from Stuart Henderson <[email protected]> -----
From: Stuart Henderson <[email protected]> Date: Sat, 17 Oct 2026 11:35:54 +0100 To: ports <[email protected]> Subject: new: textproc/py-feedparser-sgmllib Mail-Followup-To: ports <[email protected]> new port needed to update py-feedparser (diff below). ok to import? ---- Information for inst:py3-feedparser-sgmllib-2.1.0 Comment: SGML library, only for use with feedparser Description: sgmllib copied from Python 2.7 and updated. For feedparser use only. Maintainer: The OpenBSD ports mailing-list <[email protected]> WWW: https://feedparser-sgmllib.readthedocs.io/ ---- Index: Makefile =================================================================== RCS file: /cvs/ports/textproc/py-feedparser/Makefile,v diff -u -p -r1.40 Makefile --- Makefile 11 Aug 2026 12:42:48 -0000 1.40 +++ Makefile 17 Oct 2026 10:34:37 -0000 @@ -1,10 +1,9 @@ COMMENT= RSS and Atom feeds parser written in Python -MODPY_DISTV= 6.0.12 +MODPY_DISTV= 6.0.14 DISTNAME= feedparser-${MODPY_DISTV} PKGNAME= py-${DISTNAME} CATEGORIES= textproc -REVISION= 0 HOMEPAGE= https://github.com/kurtmckee/feedparser @@ -14,6 +13,10 @@ PERMIT_PACKAGE= Yes MODULES= lang/python MODPY_PYBUILD= setuptools MODPY_PI= Yes + +RUN_DEPENDS= textproc/py-feedparser-sgmllib + +TEST_DEPENDS= ${FULLPKGNAME}:${BUILD_PKGPATH} do-test: cd ${WRKSRC}; python3 tests/runtests.py Index: distinfo =================================================================== RCS file: /cvs/ports/textproc/py-feedparser/distinfo,v diff -u -p -r1.14 distinfo --- distinfo 30 Nov 2025 12:04:12 -0000 1.14 +++ distinfo 17 Oct 2026 10:34:37 -0000 @@ -1,2 +1,2 @@ -SHA256 (feedparser-6.0.12.tar.gz) = ZPds6Qrj6O9dHt4PjTtQzia8znHdiuXoKxzS1KX5Qig= -SIZE (feedparser-6.0.12.tar.gz) = 286579 +SHA256 (feedparser-6.0.14.tar.gz) = CIZ5sMS1Q+4hGoIN1URpjHakAhIurnRzwEpDQl8oPQY= +SIZE (feedparser-6.0.14.tar.gz) = 286108 Index: patches/patch-feedparser_sgml_py =================================================================== RCS file: patches/patch-feedparser_sgml_py diff -N patches/patch-feedparser_sgml_py --- patches/patch-feedparser_sgml_py 11 Mar 2022 20:03:18 -0000 1.2 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,12 +0,0 @@ -Index: feedparser/sgml.py ---- feedparser/sgml.py.orig -+++ feedparser/sgml.py -@@ -27,7 +27,7 @@ - - import re - --import sgmllib -+import feedparser.sgmllib as sgmllib - - __all__ = [ - 'sgmllib', Index: patches/patch-feedparser_sgmllib_py =================================================================== RCS file: patches/patch-feedparser_sgmllib_py diff -N patches/patch-feedparser_sgmllib_py --- patches/patch-feedparser_sgmllib_py 11 Mar 2022 20:03:18 -0000 1.2 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,554 +0,0 @@ -Quick and dirty 2to3 port of sgmllib from Python 2. -From https://pypi.org/project/sgmllib3k/ - -Index: feedparser/sgmllib.py ---- feedparser/sgmllib.py.orig -+++ feedparser/sgmllib.py -@@ -0,0 +1,547 @@ -+"""A parser for SGML, using the derived class as a static DTD.""" -+ -+# XXX This only supports those SGML features used by HTML. -+ -+# XXX There should be a way to distinguish between PCDATA (parsed -+# character data -- the normal case), RCDATA (replaceable character -+# data -- only char and entity references and end tags are special) -+# and CDATA (character data -- only end tags are special). RCDATA is -+# not supported at all. -+ -+import _markupbase -+import re -+ -+__all__ = ["SGMLParser", "SGMLParseError"] -+ -+# Regular expressions used for parsing -+ -+interesting = re.compile('[&<]') -+incomplete = re.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|' -+ '<([a-zA-Z][^<>]*|' -+ '/([a-zA-Z][^<>]*)?|' -+ '![^<>]*)?') -+ -+entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]') -+charref = re.compile('&#([0-9]+)[^0-9]') -+ -+starttagopen = re.compile('<[>a-zA-Z]') -+shorttagopen = re.compile('<[a-zA-Z][-.a-zA-Z0-9]*/') -+shorttag = re.compile('<([a-zA-Z][-.a-zA-Z0-9]*)/([^/]*)/') -+piclose = re.compile('>') -+endbracket = re.compile('[<>]') -+tagfind = re.compile('[a-zA-Z][-_.a-zA-Z0-9]*') -+attrfind = re.compile( -+ r'\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)(\s*=\s*' -+ r'(\'[^\']*\'|"[^"]*"|[][\-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?') -+ -+ -+class SGMLParseError(RuntimeError): -+ """Exception raised for all parse errors.""" -+ pass -+ -+ -+# SGML parser base class -- find tags and call handler functions. -+# Usage: p = SGMLParser(); p.feed(data); ...; p.close(). -+# The dtd is defined by deriving a class which defines methods -+# with special names to handle tags: start_foo and end_foo to handle -+# <foo> and </foo>, respectively, or do_foo to handle <foo> by itself. -+# (Tags are converted to lower case for this purpose.) The data -+# between tags is passed to the parser by calling self.handle_data() -+# with some data as argument (the data may be split up in arbitrary -+# chunks). Entity references are passed by calling -+# self.handle_entityref() with the entity reference as argument. -+ -+class SGMLParser(_markupbase.ParserBase): -+ # Definition of entities -- derived classes may override -+ entity_or_charref = re.compile('&(?:' -+ '([a-zA-Z][-.a-zA-Z0-9]*)|#([0-9]+)' -+ ')(;?)') -+ -+ def __init__(self, verbose=0): -+ """Initialize and reset this instance.""" -+ self.verbose = verbose -+ self.reset() -+ -+ def reset(self): -+ """Reset this instance. Loses all unprocessed data.""" -+ self.__starttag_text = None -+ self.rawdata = '' -+ self.stack = [] -+ self.lasttag = '???' -+ self.nomoretags = 0 -+ self.literal = 0 -+ _markupbase.ParserBase.reset(self) -+ -+ def setnomoretags(self): -+ """Enter literal mode (CDATA) till EOF. -+ -+ Intended for derived classes only. -+ """ -+ self.nomoretags = self.literal = 1 -+ -+ def setliteral(self, *args): -+ """Enter literal mode (CDATA). -+ -+ Intended for derived classes only. -+ """ -+ self.literal = 1 -+ -+ def feed(self, data): -+ """Feed some data to the parser. -+ -+ Call this as often as you want, with as little or as much text -+ as you want (may include '\n'). (This just saves the text, -+ all the processing is done by goahead().) -+ """ -+ -+ self.rawdata = self.rawdata + data -+ self.goahead(0) -+ -+ def close(self): -+ """Handle the remaining data.""" -+ self.goahead(1) -+ -+ def error(self, message): -+ raise SGMLParseError(message) -+ -+ # Internal -- handle data as far as reasonable. May leave state -+ # and data to be processed by a subsequent call. If 'end' is -+ # true, force handling all data as if followed by EOF marker. -+ def goahead(self, end): -+ rawdata = self.rawdata -+ i = 0 -+ n = len(rawdata) -+ while i < n: -+ if self.nomoretags: -+ self.handle_data(rawdata[i:n]) -+ i = n -+ break -+ match = interesting.search(rawdata, i) -+ if match: j = match.start() -+ else: j = n -+ if i < j: -+ self.handle_data(rawdata[i:j]) -+ i = j -+ if i == n: break -+ if rawdata[i] == '<': -+ if starttagopen.match(rawdata, i): -+ if self.literal: -+ self.handle_data(rawdata[i]) -+ i = i+1 -+ continue -+ k = self.parse_starttag(i) -+ if k < 0: break -+ i = k -+ continue -+ if rawdata.startswith("</", i): -+ k = self.parse_endtag(i) -+ if k < 0: break -+ i = k -+ self.literal = 0 -+ continue -+ if self.literal: -+ if n > (i + 1): -+ self.handle_data("<") -+ i = i+1 -+ else: -+ # incomplete -+ break -+ continue -+ if rawdata.startswith("<!--", i): -+ # Strictly speaking, a comment is --.*-- -+ # within a declaration tag <!...>. -+ # This should be removed, -+ # and comments handled only in parse_declaration. -+ k = self.parse_comment(i) -+ if k < 0: break -+ i = k -+ continue -+ if rawdata.startswith("<?", i): -+ k = self.parse_pi(i) -+ if k < 0: break -+ i = i+k -+ continue -+ if rawdata.startswith("<!", i): -+ # This is some sort of declaration; in "HTML as -+ # deployed," this should only be the document type -+ # declaration ("<!DOCTYPE html...>"). -+ k = self.parse_declaration(i) -+ if k < 0: break -+ i = k -+ continue -+ elif rawdata[i] == '&': -+ if self.literal: -+ self.handle_data(rawdata[i]) -+ i = i+1 -+ continue -+ match = charref.match(rawdata, i) -+ if match: -+ name = match.group(1) -+ self.handle_charref(name) -+ i = match.end(0) -+ if rawdata[i-1] != ';': i = i-1 -+ continue -+ match = entityref.match(rawdata, i) -+ if match: -+ name = match.group(1) -+ self.handle_entityref(name) -+ i = match.end(0) -+ if rawdata[i-1] != ';': i = i-1 -+ continue -+ else: -+ self.error('neither < nor & ??') -+ # We get here only if incomplete matches but -+ # nothing else -+ match = incomplete.match(rawdata, i) -+ if not match: -+ self.handle_data(rawdata[i]) -+ i = i+1 -+ continue -+ j = match.end(0) -+ if j == n: -+ break # Really incomplete -+ self.handle_data(rawdata[i:j]) -+ i = j -+ # end while -+ if end and i < n: -+ self.handle_data(rawdata[i:n]) -+ i = n -+ self.rawdata = rawdata[i:] -+ # XXX if end: check for empty stack -+ -+ # Extensions for the DOCTYPE scanner: -+ _decl_otherchars = '=' -+ -+ # Internal -- parse processing instr, return length or -1 if not terminated -+ def parse_pi(self, i): -+ rawdata = self.rawdata -+ if rawdata[i:i+2] != '<?': -+ self.error('unexpected call to parse_pi()') -+ match = piclose.search(rawdata, i+2) -+ if not match: -+ return -1 -+ j = match.start(0) -+ self.handle_pi(rawdata[i+2: j]) -+ j = match.end(0) -+ return j-i -+ -+ def get_starttag_text(self): -+ return self.__starttag_text -+ -+ # Internal -- handle starttag, return length or -1 if not terminated -+ def parse_starttag(self, i): -+ self.__starttag_text = None -+ start_pos = i -+ rawdata = self.rawdata -+ if shorttagopen.match(rawdata, i): -+ # SGML shorthand: <tag/data/ == <tag>data</tag> -+ # XXX Can data contain &... (entity or char refs)? -+ # XXX Can data contain < or > (tag characters)? -+ # XXX Can there be whitespace before the first /? -+ match = shorttag.match(rawdata, i) -+ if not match: -+ return -1 -+ tag, data = match.group(1, 2) -+ self.__starttag_text = '<%s/' % tag -+ tag = tag.lower() -+ k = match.end(0) -+ self.finish_shorttag(tag, data) -+ self.__starttag_text = rawdata[start_pos:match.end(1) + 1] -+ return k -+ # XXX The following should skip matching quotes (' or ") -+ # As a shortcut way to exit, this isn't so bad, but shouldn't -+ # be used to locate the actual end of the start tag since the -+ # < or > characters may be embedded in an attribute value. -+ match = endbracket.search(rawdata, i+1) -+ if not match: -+ return -1 -+ j = match.start(0) -+ # Now parse the data between i+1 and j into a tag and attrs -+ attrs = [] -+ if rawdata[i:i+2] == '<>': -+ # SGML shorthand: <> == <last open tag seen> -+ k = j -+ tag = self.lasttag -+ else: -+ match = tagfind.match(rawdata, i+1) -+ if not match: -+ self.error('unexpected call to parse_starttag') -+ k = match.end(0) -+ tag = rawdata[i+1:k].lower() -+ self.lasttag = tag -+ while k < j: -+ match = attrfind.match(rawdata, k) -+ if not match: break -+ attrname, rest, attrvalue = match.group(1, 2, 3) -+ if not rest: -+ attrvalue = attrname -+ else: -+ if (attrvalue[:1] == "'" == attrvalue[-1:] or -+ attrvalue[:1] == '"' == attrvalue[-1:]): -+ # strip quotes -+ attrvalue = attrvalue[1:-1] -+ attrvalue = self.entity_or_charref.sub( -+ self._convert_ref, attrvalue) -+ attrs.append((attrname.lower(), attrvalue)) -+ k = match.end(0) -+ if rawdata[j] == '>': -+ j = j+1 -+ self.__starttag_text = rawdata[start_pos:j] -+ self.finish_starttag(tag, attrs) -+ return j -+ -+ # Internal -- convert entity or character reference -+ def _convert_ref(self, match): -+ if match.group(2): -+ return self.convert_charref(match.group(2)) or \ -+ '&#%s%s' % match.groups()[1:] -+ elif match.group(3): -+ return self.convert_entityref(match.group(1)) or \ -+ '&%s;' % match.group(1) -+ else: -+ return '&%s' % match.group(1) -+ -+ # Internal -- parse endtag -+ def parse_endtag(self, i): -+ rawdata = self.rawdata -+ match = endbracket.search(rawdata, i+1) -+ if not match: -+ return -1 -+ j = match.start(0) -+ tag = rawdata[i+2:j].strip().lower() -+ if rawdata[j] == '>': -+ j = j+1 -+ self.finish_endtag(tag) -+ return j -+ -+ # Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>) -+ def finish_shorttag(self, tag, data): -+ self.finish_starttag(tag, []) -+ self.handle_data(data) -+ self.finish_endtag(tag) -+ -+ # Internal -- finish processing of start tag -+ # Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag -+ def finish_starttag(self, tag, attrs): -+ try: -+ method = getattr(self, 'start_' + tag) -+ except AttributeError: -+ try: -+ method = getattr(self, 'do_' + tag) -+ except AttributeError: -+ self.unknown_starttag(tag, attrs) -+ return -1 -+ else: -+ self.handle_starttag(tag, method, attrs) -+ return 0 -+ else: -+ self.stack.append(tag) -+ self.handle_starttag(tag, method, attrs) -+ return 1 -+ -+ # Internal -- finish processing of end tag -+ def finish_endtag(self, tag): -+ if not tag: -+ found = len(self.stack) - 1 -+ if found < 0: -+ self.unknown_endtag(tag) -+ return -+ else: -+ if tag not in self.stack: -+ try: -+ method = getattr(self, 'end_' + tag) -+ except AttributeError: -+ self.unknown_endtag(tag) -+ else: -+ self.report_unbalanced(tag) -+ return -+ found = len(self.stack) -+ for i in range(found): -+ if self.stack[i] == tag: found = i -+ while len(self.stack) > found: -+ tag = self.stack[-1] -+ try: -+ method = getattr(self, 'end_' + tag) -+ except AttributeError: -+ method = None -+ if method: -+ self.handle_endtag(tag, method) -+ else: -+ self.unknown_endtag(tag) -+ del self.stack[-1] -+ -+ # Overridable -- handle start tag -+ def handle_starttag(self, tag, method, attrs): -+ method(attrs) -+ -+ # Overridable -- handle end tag -+ def handle_endtag(self, tag, method): -+ method() -+ -+ # Example -- report an unbalanced </...> tag. -+ def report_unbalanced(self, tag): -+ if self.verbose: -+ print('*** Unbalanced </' + tag + '>') -+ print('*** Stack:', self.stack) -+ -+ def convert_charref(self, name): -+ """Convert character reference, may be overridden.""" -+ try: -+ n = int(name) -+ except ValueError: -+ return -+ if not 0 <= n <= 127: -+ return -+ return self.convert_codepoint(n) -+ -+ def convert_codepoint(self, codepoint): -+ return chr(codepoint) -+ -+ def handle_charref(self, name): -+ """Handle character reference, no need to override.""" -+ replacement = self.convert_charref(name) -+ if replacement is None: -+ self.unknown_charref(name) -+ else: -+ self.handle_data(replacement) -+ -+ # Definition of entities -- derived classes may override -+ entitydefs = \ -+ {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': '\''} -+ -+ def convert_entityref(self, name): -+ """Convert entity references. -+ -+ As an alternative to overriding this method; one can tailor the -+ results by setting up the self.entitydefs mapping appropriately. -+ """ -+ table = self.entitydefs -+ if name in table: -+ return table[name] -+ else: -+ return -+ -+ def handle_entityref(self, name): -+ """Handle entity references, no need to override.""" -+ replacement = self.convert_entityref(name) -+ if replacement is None: -+ self.unknown_entityref(name) -+ else: -+ self.handle_data(replacement) -+ -+ # Example -- handle data, should be overridden -+ def handle_data(self, data): -+ pass -+ -+ # Example -- handle comment, could be overridden -+ def handle_comment(self, data): -+ pass -+ -+ # Example -- handle declaration, could be overridden -+ def handle_decl(self, decl): -+ pass -+ -+ # Example -- handle processing instruction, could be overridden -+ def handle_pi(self, data): -+ pass -+ -+ # To be overridden -- handlers for unknown objects -+ def unknown_starttag(self, tag, attrs): pass -+ def unknown_endtag(self, tag): pass -+ def unknown_charref(self, ref): pass -+ def unknown_entityref(self, ref): pass -+ -+ -+class TestSGMLParser(SGMLParser): -+ -+ def __init__(self, verbose=0): -+ self.testdata = "" -+ SGMLParser.__init__(self, verbose) -+ -+ def handle_data(self, data): -+ self.testdata = self.testdata + data -+ if len(repr(self.testdata)) >= 70: -+ self.flush() -+ -+ def flush(self): -+ data = self.testdata -+ if data: -+ self.testdata = "" -+ print('data:', repr(data)) -+ -+ def handle_comment(self, data): -+ self.flush() -+ r = repr(data) -+ if len(r) > 68: -+ r = r[:32] + '...' + r[-32:] -+ print('comment:', r) -+ -+ def unknown_starttag(self, tag, attrs): -+ self.flush() -+ if not attrs: -+ print('start tag: <' + tag + '>') -+ else: -+ print('start tag: <' + tag, end=' ') -+ for name, value in attrs: -+ print(name + '=' + '"' + value + '"', end=' ') -+ print('>') -+ -+ def unknown_endtag(self, tag): -+ self.flush() -+ print('end tag: </' + tag + '>') -+ -+ def unknown_entityref(self, ref): -+ self.flush() -+ print('*** unknown entity ref: &' + ref + ';') -+ -+ def unknown_charref(self, ref): -+ self.flush() -+ print('*** unknown char ref: &#' + ref + ';') -+ -+ def unknown_decl(self, data): -+ self.flush() -+ print('*** unknown decl: [' + data + ']') -+ -+ def close(self): -+ SGMLParser.close(self) -+ self.flush() -+ -+ -+def test(args = None): -+ import sys -+ -+ if args is None: -+ args = sys.argv[1:] -+ -+ if args and args[0] == '-s': -+ args = args[1:] -+ klass = SGMLParser -+ else: -+ klass = TestSGMLParser -+ -+ if args: -+ file = args[0] -+ else: -+ file = 'test.html' -+ -+ if file == '-': -+ f = sys.stdin -+ else: -+ try: -+ f = open(file, 'r') -+ except IOError as msg: -+ print(file, ":", msg) -+ sys.exit(1) -+ -+ data = f.read() -+ if f is not sys.stdin: -+ f.close() -+ -+ x = klass() -+ for c in data: -+ x.feed(c) -+ x.close() -+ -+ -+if __name__ == '__main__': -+ test() Index: pkg/PLIST =================================================================== RCS file: /cvs/ports/textproc/py-feedparser/pkg/PLIST,v diff -u -p -r1.13 PLIST --- pkg/PLIST 11 Aug 2026 12:42:48 -0000 1.13 +++ pkg/PLIST 17 Oct 2026 10:34:37 -0000 @@ -26,8 +26,6 @@ lib/python${MODPY_VERSION}/site-packages lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}sanitizer.${MODPY_PYC_MAGIC_TAG}pyc lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}sgml.${MODPY_PYC_MAGIC_TAG}${MODPY_PYOEXTENSION} lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}sgml.${MODPY_PYC_MAGIC_TAG}pyc -lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}sgmllib.${MODPY_PYC_MAGIC_TAG}${MODPY_PYOEXTENSION} -lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}sgmllib.${MODPY_PYC_MAGIC_TAG}pyc lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}urls.${MODPY_PYC_MAGIC_TAG}${MODPY_PYOEXTENSION} lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}urls.${MODPY_PYC_MAGIC_TAG}pyc lib/python${MODPY_VERSION}/site-packages/feedparser/${MODPY_PYCACHE}util.${MODPY_PYC_MAGIC_TAG}${MODPY_PYOEXTENSION} @@ -109,6 +107,5 @@ lib/python${MODPY_VERSION}/site-packages lib/python${MODPY_VERSION}/site-packages/feedparser/parsers/strict.py lib/python${MODPY_VERSION}/site-packages/feedparser/sanitizer.py lib/python${MODPY_VERSION}/site-packages/feedparser/sgml.py -lib/python${MODPY_VERSION}/site-packages/feedparser/sgmllib.py lib/python${MODPY_VERSION}/site-packages/feedparser/urls.py lib/python${MODPY_VERSION}/site-packages/feedparser/util.py ----- End forwarded message -----
py-feedparser-sgmllib.tgz
Description: application/tar-gz
