diff options
| author | David Robillard <d@drobilla.net> | 2026-02-07 19:54:17 -0500 |
|---|---|---|
| committer | David Robillard <d@drobilla.net> | 2026-02-07 20:41:30 -0500 |
| commit | f972e9ffa4678492c9fd3939f6156f209af4e674 (patch) | |
| tree | 9e6e8576d9598ccc6f25a6f5db2349e6d998d6fe | |
| parent | 86a8bb5d103f749017e6288dbce9bbe981ed9955 (diff) | |
| download | lv2-f972e9ffa4678492c9fd3939f6156f209af4e674.tar.xz | |
lv2specgen: Fix offline XHTML validation and make it optional
| -rw-r--r-- | NEWS | 3 | ||||
| -rw-r--r-- | lv2specgen/DTD/xhtml-rdfa-1.dtd | 2 | ||||
| -rw-r--r-- | lv2specgen/DTD/xhtml-ruby-1.mod | 242 | ||||
| -rwxr-xr-x | lv2specgen/lv2specgen.py | 75 | ||||
| -rw-r--r-- | lv2specgen/meson.build | 3 |
5 files changed, 295 insertions, 30 deletions
@@ -9,9 +9,10 @@ lv2 (1.18.11) unstable; urgency=medium * Override pkg-config dependency within meson * Remove troublesome lv2_atom_assert_double_fits_in_64_bits * eg-metro: Fix memory leak + * lv2specgen: Fix offline XHTML validation and make it optional * ui: Add types for Gtk4UI and Qt6UI - -- David Robillard <d@drobilla.net> Thu, 13 Nov 2025 22:54:05 +0000 + -- David Robillard <d@drobilla.net> Sun, 08 Feb 2026 00:37:03 +0000 lv2 (1.18.10) stable; urgency=medium diff --git a/lv2specgen/DTD/xhtml-rdfa-1.dtd b/lv2specgen/DTD/xhtml-rdfa-1.dtd index 53890bb..0490b09 100644 --- a/lv2specgen/DTD/xhtml-rdfa-1.dtd +++ b/lv2specgen/DTD/xhtml-rdfa-1.dtd @@ -218,7 +218,7 @@ <![%xhtml-ruby.module;[ <!ENTITY % xhtml-ruby.mod PUBLIC "-//W3C//ELEMENTS XHTML Ruby 1.0//EN" - "http://www.w3.org/TR/ruby/xhtml-ruby-1.mod" > + "xhtml-ruby-1.mod" > %xhtml-ruby.mod;]]> <!-- Presentation Module ........................................ --> diff --git a/lv2specgen/DTD/xhtml-ruby-1.mod b/lv2specgen/DTD/xhtml-ruby-1.mod new file mode 100644 index 0000000..978da8d --- /dev/null +++ b/lv2specgen/DTD/xhtml-ruby-1.mod @@ -0,0 +1,242 @@ +<!-- ...................................................................... --> +<!-- XHTML Ruby Module .................................................... --> +<!-- file: xhtml-ruby-1.mod + + This is XHTML, a reformulation of HTML as a modular XML application. + Copyright 1999-2001 W3C (MIT, INRIA, Keio), All Rights Reserved. + Revision: $Id: xhtml-ruby-1.mod,v 1.1 2008/06/21 19:42:10 smccarro Exp $ + + This module is based on the W3C Ruby Annotation Specification: + + http://www.w3.org/TR/ruby + + This DTD module is identified by the PUBLIC and SYSTEM identifiers: + + PUBLIC "-//W3C//ELEMENTS XHTML Ruby 1.0//EN" + SYSTEM "http://www.w3.org/TR/ruby/xhtml-ruby-1.mod" + + ...................................................................... --> + +<!-- Ruby Elements + + ruby, rbc, rtc, rb, rt, rp + + This module declares the elements and their attributes used to + support ruby annotation markup. +--> + +<!-- declare qualified element type names: +--> +<!ENTITY % ruby.qname "ruby" > +<!ENTITY % rbc.qname "rbc" > +<!ENTITY % rtc.qname "rtc" > +<!ENTITY % rb.qname "rb" > +<!ENTITY % rt.qname "rt" > +<!ENTITY % rp.qname "rp" > + +<!-- rp fallback is included by default. +--> +<!ENTITY % Ruby.fallback "INCLUDE" > +<!ENTITY % Ruby.fallback.mandatory "IGNORE" > + +<!-- Complex ruby is included by default; it may be + overridden by other modules to ignore it. +--> +<!ENTITY % Ruby.complex "INCLUDE" > + +<!-- Fragments for the content model of the ruby element --> +<![%Ruby.fallback;[ +<![%Ruby.fallback.mandatory;[ +<!ENTITY % Ruby.content.simple + "( %rb.qname;, %rp.qname;, %rt.qname;, %rp.qname; )" +> +]]> +<!ENTITY % Ruby.content.simple + "( %rb.qname;, ( %rt.qname; | ( %rp.qname;, %rt.qname;, %rp.qname; ) ) )" +> +]]> +<!ENTITY % Ruby.content.simple "( %rb.qname;, %rt.qname; )" > + +<![%Ruby.complex;[ +<!ENTITY % Ruby.content.complex + "| ( %rbc.qname;, %rtc.qname;, %rtc.qname;? )" +> +]]> +<!ENTITY % Ruby.content.complex "" > + +<!-- Content models of the rb and the rt elements are intended to + allow other inline-level elements of its parent markup language, + but it should not include ruby descendent elements. The following + parameter entity %NoRuby.content; can be used to redefine + those content models with minimum effort. It's defined as + '( #PCDATA )' by default. +--> +<!ENTITY % NoRuby.content "( #PCDATA )" > + +<!-- one or more digits (NUMBER) --> +<!ENTITY % Number.datatype "CDATA" > + +<!-- ruby element ...................................... --> + +<!ENTITY % ruby.element "INCLUDE" > +<![%ruby.element;[ +<!ENTITY % ruby.content + "( %Ruby.content.simple; %Ruby.content.complex; )" +> +<!ELEMENT %ruby.qname; %ruby.content; > +<!-- end of ruby.element -->]]> + +<![%Ruby.complex;[ +<!-- rbc (ruby base component) element ................. --> + +<!ENTITY % rbc.element "INCLUDE" > +<![%rbc.element;[ +<!ENTITY % rbc.content + "(%rb.qname;)+" +> +<!ELEMENT %rbc.qname; %rbc.content; > +<!-- end of rbc.element -->]]> + +<!-- rtc (ruby text component) element ................. --> + +<!ENTITY % rtc.element "INCLUDE" > +<![%rtc.element;[ +<!ENTITY % rtc.content + "(%rt.qname;)+" +> +<!ELEMENT %rtc.qname; %rtc.content; > +<!-- end of rtc.element -->]]> +]]> + +<!-- rb (ruby base) element ............................ --> + +<!ENTITY % rb.element "INCLUDE" > +<![%rb.element;[ +<!-- %rb.content; uses %NoRuby.content; as its content model, + which is '( #PCDATA )' by default. It may be overridden + by other modules to allow other inline-level elements + of its parent markup language, but it should not include + ruby descendent elements. +--> +<!ENTITY % rb.content "%NoRuby.content;" > +<!ELEMENT %rb.qname; %rb.content; > +<!-- end of rb.element -->]]> + +<!-- rt (ruby text) element ............................ --> + +<!ENTITY % rt.element "INCLUDE" > +<![%rt.element;[ +<!-- %rt.content; uses %NoRuby.content; as its content model, + which is '( #PCDATA )' by default. It may be overridden + by other modules to allow other inline-level elements + of its parent markup language, but it should not include + ruby descendent elements. +--> +<!ENTITY % rt.content "%NoRuby.content;" > + +<!ELEMENT %rt.qname; %rt.content; > +<!-- end of rt.element -->]]> + +<!-- rbspan attribute is used for complex ruby only ...... --> +<![%Ruby.complex;[ +<!ENTITY % rt.attlist "INCLUDE" > +<![%rt.attlist;[ +<!ATTLIST %rt.qname; + rbspan %Number.datatype; "1" +> +<!-- end of rt.attlist -->]]> +]]> + +<!-- rp (ruby parenthesis) element ..................... --> + +<![%Ruby.fallback;[ +<!ENTITY % rp.element "INCLUDE" > +<![%rp.element;[ +<!ENTITY % rp.content + "( #PCDATA )" +> +<!ELEMENT %rp.qname; %rp.content; > +<!-- end of rp.element -->]]> +]]> + +<!-- Ruby Common Attributes + + The following optional ATTLIST declarations provide an easy way + to define common attributes for ruby elements. These declarations + are ignored by default. + + Ruby elements are intended to have common attributes of its + parent markup language. For example, if a markup language defines + common attributes as a parameter entity %attrs;, you may add + those attributes by just declaring the following parameter entities + + <!ENTITY % Ruby.common.attlists "INCLUDE" > + <!ENTITY % Ruby.common.attrib "%attrs;" > + + before including the Ruby module. +--> + +<!ENTITY % Ruby.common.attlists "IGNORE" > +<![%Ruby.common.attlists;[ +<!ENTITY % Ruby.common.attrib "" > + +<!-- common attributes for ruby ........................ --> + +<!ENTITY % Ruby.common.attlist "INCLUDE" > +<![%Ruby.common.attlist;[ +<!ATTLIST %ruby.qname; + %Ruby.common.attrib; +> +<!-- end of Ruby.common.attlist -->]]> + +<![%Ruby.complex;[ +<!-- common attributes for rbc ......................... --> + +<!ENTITY % Rbc.common.attlist "INCLUDE" > +<![%Rbc.common.attlist;[ +<!ATTLIST %rbc.qname; + %Ruby.common.attrib; +> +<!-- end of Rbc.common.attlist -->]]> + +<!-- common attributes for rtc ......................... --> + +<!ENTITY % Rtc.common.attlist "INCLUDE" > +<![%Rtc.common.attlist;[ +<!ATTLIST %rtc.qname; + %Ruby.common.attrib; +> +<!-- end of Rtc.common.attlist -->]]> +]]> + +<!-- common attributes for rb .......................... --> + +<!ENTITY % Rb.common.attlist "INCLUDE" > +<![%Rb.common.attlist;[ +<!ATTLIST %rb.qname; + %Ruby.common.attrib; +> +<!-- end of Rb.common.attlist -->]]> + +<!-- common attributes for rt .......................... --> + +<!ENTITY % Rt.common.attlist "INCLUDE" > +<![%Rt.common.attlist;[ +<!ATTLIST %rt.qname; + %Ruby.common.attrib; +> +<!-- end of Rt.common.attlist -->]]> + +<![%Ruby.fallback;[ +<!-- common attributes for rp .......................... --> + +<!ENTITY % Rp.common.attlist "INCLUDE" > +<![%Rp.common.attlist;[ +<!ATTLIST %rp.qname; + %Ruby.common.attrib; +> +<!-- end of Rp.common.attlist -->]]> +]]> +]]> + +<!-- end of xhtml-ruby-1.mod --> diff --git a/lv2specgen/lv2specgen.py b/lv2specgen/lv2specgen.py index 7927261..3eee372 100755 --- a/lv2specgen/lv2specgen.py +++ b/lv2specgen/lv2specgen.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2009-2022 David Robillard <d@drobilla.net> +# Copyright 2009-2026 David Robillard <d@drobilla.net> # Copyright 2003-2008 Christopher Schmidt <crschmidt@crschmidt.net> # Copyright 2005-2008 Uldis Bojars <uldis.bojars@deri.org> # Copyright 2007-2008 Sergio Fernández <sergio.fernandez@fundacionctic.org> @@ -18,9 +18,11 @@ # pylint: disable=global-statement # pylint: disable=global-variable-not-assigned # pylint: disable=invalid-name +# pylint: disable=missing-class-docstring # pylint: disable=missing-function-docstring # pylint: disable=no-member # pylint: disable=redefined-outer-name +# pylint: disable=too-few-public-methods # pylint: disable=too-many-arguments # pylint: disable=too-many-boolean-expressions # pylint: disable=too-many-branches @@ -43,7 +45,7 @@ import xml.sax.saxutils import markdown import markdown.extensions -__date__ = "2011-10-26" +__date__ = "2026-02-07" __version__ = __date__.replace("-", ".") __authors__ = """ Christopher Schmidt, @@ -104,6 +106,12 @@ doap = rdflib.Namespace("http://usefulinc.com/ns/doap#") foaf = rdflib.Namespace("http://xmlns.com/foaf/0.1/") +class DTDResolver(etree.Resolver): + def resolve(self, url, _pubid, context): + path = os.path.join(specgendir, url) + return self.resolve_filename(path, context) + + def findStatements(model, s, p, o): return model.triples([s, p, o]) @@ -1160,6 +1168,7 @@ def specgen( tags, opts, instances=False, + validate=False, ): """The meat and potatoes: Everything starts here.""" @@ -1346,17 +1355,22 @@ def specgen( build_date = datetime.datetime.fromtimestamp(now, datetime.timezone.utc) template = template.replace("@DATE@", build_date.strftime("%F")) template = template.replace("@TIME@", build_date.strftime("%F %H:%M UTC")) + if not validate: + return template # Validate complete output page oldcwd = os.getcwd() try: os.chdir(specgendir) + parser = etree.XMLParser(dtd_validation=True, no_network=True) + parser.resolvers.add(DTDResolver()) + etree.fromstring( template.replace( '"http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd"', '"DTD/xhtml-rdfa-1.dtd"', ).encode("utf-8"), - etree.XMLParser(dtd_validation=True, no_network=True), + parser, ) except Exception as e: sys.stderr.write( @@ -1429,31 +1443,31 @@ def _data_dirs(): if __name__ == "__main__": - data_dir = None - for d in _data_dirs(): - path = os.path.join(d, "lv2specgen") - if ( - os.path.exists(os.path.join(path, "template.html")) - and os.path.exists(os.path.join(path, "style.css")) - and os.path.exists(os.path.join(path, "pygments.css")) - ): - data_dir = path - break - - if data_dir: - # Use installed files - specgendir = data_dir - default_template_path = os.path.join(data_dir, "template.html") - default_style_dir = data_dir + script_path = os.path.realpath(__file__) + script_dir = os.path.dirname(os.path.realpath(__file__)) + if os.path.exists(os.path.join(script_dir, "template.html")): + # Run from source repository + specgendir = script_dir + lv2_source_root = os.path.dirname(script_dir) + default_template_path = os.path.join(script_dir, "template.html") + default_style_dir = os.path.join(lv2_source_root, "doc", "style") else: - script_path = os.path.realpath(__file__) - script_dir = os.path.dirname(os.path.realpath(__file__)) - if os.path.exists(os.path.join(script_dir, "template.html")): - # Run from source repository - specgendir = script_dir - lv2_source_root = os.path.dirname(script_dir) - default_template_path = os.path.join(script_dir, "template.html") - default_style_dir = os.path.join(lv2_source_root, "doc", "style") + data_dir = None + for d in _data_dirs(): + path = os.path.join(d, "lv2specgen") + files_exist = all( + os.path.exists(os.path.join(path, name)) + for name in ["template.html", "style.css", "pygments.css"] + ) + if files_exist: + data_dir = path + break + + if data_dir: + # Use installed files + specgendir = data_dir + default_template_path = os.path.join(data_dir, "template.html") + default_style_dir = data_dir else: sys.stderr.write("error: Unable to find lv2specgen data\n") sys.exit(-2) @@ -1529,6 +1543,12 @@ if __name__ == "__main__": dest="copy_style", help="Copy style from template directory to output directory", ) + opt.add_option( + "--validate", + action="store_true", + dest="validate", + help="Validate against local XML DTDs", + ) (options, args) = opt.parse_args() opts = vars(options) @@ -1564,6 +1584,7 @@ if __name__ == "__main__": tags, opts, instances=True, + validate=opts["validate"], ) # Save to HTML output file diff --git a/lv2specgen/meson.build b/lv2specgen/meson.build index bc3a616..55cdecd 100644 --- a/lv2specgen/meson.build +++ b/lv2specgen/meson.build @@ -1,4 +1,4 @@ -# Copyright 2022-2025 David Robillard <d@drobilla.net> +# Copyright 2022-2026 David Robillard <d@drobilla.net> # SPDX-License-Identifier: 0BSD OR ISC lv2specgen_py = files('lv2specgen.py') @@ -12,6 +12,7 @@ lv2specgen_command_prefix = [ '--list-page=' + lv2_list_page, '--style-dir=' + lv2_source_root / 'doc' / 'style', '--template', files('template.html'), + '--validate', ] if is_variable('lv2_tags') |