aboutsummaryrefslogtreecommitdiffstats
path: root/lv2specgen
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2026-02-07 19:54:17 -0500
committerDavid Robillard <d@drobilla.net>2026-02-07 20:41:30 -0500
commitf972e9ffa4678492c9fd3939f6156f209af4e674 (patch)
tree9e6e8576d9598ccc6f25a6f5db2349e6d998d6fe /lv2specgen
parent86a8bb5d103f749017e6288dbce9bbe981ed9955 (diff)
downloadlv2-f972e9ffa4678492c9fd3939f6156f209af4e674.tar.xz
lv2specgen: Fix offline XHTML validation and make it optional
Diffstat (limited to 'lv2specgen')
-rw-r--r--lv2specgen/DTD/xhtml-rdfa-1.dtd2
-rw-r--r--lv2specgen/DTD/xhtml-ruby-1.mod242
-rwxr-xr-xlv2specgen/lv2specgen.py75
-rw-r--r--lv2specgen/meson.build3
4 files changed, 293 insertions, 29 deletions
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')