oapi-profile-builder 3.3.0__tar.gz → 3.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {oapi_profile_builder-3.3.0/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.4.1}/PKG-INFO +49 -2
  2. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/README.md +48 -1
  3. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/pyproject.toml +1 -1
  4. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/cover.py +11 -7
  5. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/generate.py +215 -3
  6. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/models.py +35 -5
  7. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1/src/oapi_profile_builder.egg-info}/PKG-INFO +49 -2
  8. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/LICENSE +0 -0
  9. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/setup.cfg +0 -0
  10. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/__init__.py +0 -0
  11. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/cite.py +0 -0
  12. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/cite_features.py +0 -0
  13. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/cli.py +0 -0
  14. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/compile.py +0 -0
  15. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder/server_validation.py +0 -0
  16. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
  17. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
  18. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
  19. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
  20. {oapi_profile_builder-3.3.0 → oapi_profile_builder-3.4.1}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oapi-profile-builder
3
- Version: 3.3.0
3
+ Version: 3.4.1
4
4
  Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
5
5
  Author-email: Shane Mill <shane.mill@noaa.gov>
6
6
  License: Apache License
@@ -239,15 +239,21 @@ Everything about the document — cover, title-page metadata, legal page, footer
239
239
  | PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
240
240
  | Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
241
241
  | Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
242
+ | Cover layout (logo size/position, tagline/title size, bold edition) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, bold_edition}` | Generated cover image |
243
+ | Cover date | `document_metadata.doc_pub_date` | Long-format date on the generated cover + `:published-date:` |
242
244
  | Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
243
245
  | Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
246
+ | Remove the standalone section-divider pages | `document_metadata.suppress_section_divider_pages` | `:pdf-stylesheet-override:` |
247
+ | Inline section headings (`1 Scope` in one line, no circle) | `document_metadata.inline_section_headers` | `:pdf-stylesheet-override:` |
248
+ | Flatten reference hanging indent (title under authors) | `document_metadata.suppress_bibliography_indent` | `:pdf-stylesheet-override:` |
244
249
  | Every-page DRAFT-style watermark (over content) | `document_metadata.page_watermark` | `:pdf-stylesheet-override:` (footer overlay) |
245
250
  | Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
246
251
  | Submitter table, notice paragraph, normative refs, extra terms | `document_metadata.{submitters, notice, normative_references, terms}` | Metanorma attributes / sections |
252
+ | Features `/items` paging (`limit` parameter) | `paging.{enabled, default_limit, max_limit}` | OpenAPI query parameter |
247
253
 
248
254
  Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example. Omit all of these fields and the output keeps the standard OGC house style.
249
255
 
250
- > The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
256
+ > The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, `suppress_section_divider_pages`, `inline_section_headers`, `suppress_bibliography_indent`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`, the `sections` mode, `fmt-title`, and `bibitem-normative-style`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
251
257
 
252
258
  ---
253
259
 
@@ -341,6 +347,7 @@ abstract_tests: []
341
347
  | `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
342
348
  | `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
343
349
  | `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
350
+ | `paging` | object | no | Features `/items` paging — adds a validated `limit` query parameter to the generated OpenAPI (see below) |
344
351
  | `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
345
352
 
346
353
  ---
@@ -624,6 +631,21 @@ Generates: `/processes/compute-difference`, `/processes/compute-difference/execu
624
631
 
625
632
  ---
626
633
 
634
+ ### `paging`
635
+
636
+ Controls Features `/items` paging in the generated OpenAPI. When enabled (the default), a validated `limit` query parameter is added to every `/items` endpoint (collection-level and instance-level).
637
+
638
+ ```yaml
639
+ paging:
640
+ enabled: true # default true; set false to omit the limit parameter
641
+ default_limit: 10 # OpenAPI schema default for limit (default 10)
642
+ max_limit: 100 # OpenAPI schema maximum for limit (default 10000)
643
+ ```
644
+
645
+ The emitted parameter is `limit` (`in: query`, `type: integer`, `minimum: 1`, `maximum: <max_limit>`, `default: <default_limit>`).
646
+
647
+ ---
648
+
627
649
  ### `pubsub`
628
650
 
629
651
  When present, generates `asyncapi.yaml` alongside `openapi.yaml`.
@@ -669,6 +691,8 @@ document_metadata:
669
691
  submission_date: "2026-05-01" # → :received-date:
670
692
  approval_date: "2026-06-15" # → :issued-date:
671
693
  publication_date: "2026-07-01" # → :published-date:
694
+ doc_pub_date: "2026-07-16" # optional; overrides publication_date for the cover
695
+ # (long-format "16 July 2026") and :published-date:
672
696
  # Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
673
697
  # legal notice itself is generated by Metanorma from doc_type/status.
674
698
  notice: >
@@ -707,6 +731,12 @@ document_metadata:
707
731
  font_bold: "Source Sans Pro" # optional bold face; derived from font_regular when unset
708
732
  font_italic: "Source Sans Pro" # optional italic face for the tagline
709
733
  watermark: "DRAFT" # optional diagonal watermark stamped on the cover
734
+ # --- cover layout (all optional) ---
735
+ logo_width: 200 # target logo width in px (default 420)
736
+ logo_y: 100 # logo vertical offset from top in px (default 180)
737
+ tagline_font_size: 20 # tagline point size (default 30)
738
+ title_font_size: 40 # title point size (default 54)
739
+ bold_edition: true # render the "Edition X" line in bold (default false)
710
740
  colors:
711
741
  text: "#000000" # body text
712
742
  cover_text: "#1F3864" # cover text / section numbers / ToC
@@ -763,6 +793,23 @@ With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_
763
793
 
764
794
  > These design-element overrides (and the `page_watermark` footer overlay) reach into the OGC Metanorma flavor's XSL template names and are coupled to that flavor; a future metanorma-ogc release could require refreshing them.
765
795
 
796
+ #### Structural layout changes (divider pages, inline headers, references)
797
+
798
+ `suppress_design_elements` only changes *decorative* styling — it keeps the document's structure (standalone section-divider pages, the standard heading layout, and the reference hanging indent) intact. The following are **independent, opt-in** structural changes, so the default OGC output is unaffected unless you set them:
799
+
800
+ - **`suppress_section_divider_pages: true`** — removes the standalone full-page section dividers, so each section's content follows its heading directly.
801
+ - **`inline_section_headers: true`** — renders Level 1/2 headings inline with the title (e.g. `1 Scope`, `i Abstract`) rather than the OGC circled-number layout.
802
+ - **`suppress_bibliography_indent: true`** — flattens both the Normative References and the Bibliography so each entry is a single flush paragraph, removing the hanging indent that placed the title under the authors.
803
+
804
+ ```yaml
805
+ document_metadata:
806
+ suppress_section_divider_pages: true
807
+ inline_section_headers: true
808
+ suppress_bibliography_indent: true
809
+ ```
810
+
811
+ Additionally, when `copyright_holder` is set to a non-OGC organisation, the flavor's auto-generated "Submitting Organizations" sentence ("…submitted this Document to the Open Geospatial Consortium (OGC)") is rebranded to name that organisation instead, so no residual OGC text remains in the preface.
812
+
766
813
  #### Extending Terms and Definitions
767
814
 
768
815
  Add profile-specific terms (e.g. EDR Part 3 or Pub/Sub terms) appended after the base terms in the Terms and Definitions section:
@@ -192,15 +192,21 @@ Everything about the document — cover, title-page metadata, legal page, footer
192
192
  | PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
193
193
  | Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
194
194
  | Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
195
+ | Cover layout (logo size/position, tagline/title size, bold edition) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, bold_edition}` | Generated cover image |
196
+ | Cover date | `document_metadata.doc_pub_date` | Long-format date on the generated cover + `:published-date:` |
195
197
  | Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
196
198
  | Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
199
+ | Remove the standalone section-divider pages | `document_metadata.suppress_section_divider_pages` | `:pdf-stylesheet-override:` |
200
+ | Inline section headings (`1 Scope` in one line, no circle) | `document_metadata.inline_section_headers` | `:pdf-stylesheet-override:` |
201
+ | Flatten reference hanging indent (title under authors) | `document_metadata.suppress_bibliography_indent` | `:pdf-stylesheet-override:` |
197
202
  | Every-page DRAFT-style watermark (over content) | `document_metadata.page_watermark` | `:pdf-stylesheet-override:` (footer overlay) |
198
203
  | Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
199
204
  | Submitter table, notice paragraph, normative refs, extra terms | `document_metadata.{submitters, notice, normative_references, terms}` | Metanorma attributes / sections |
205
+ | Features `/items` paging (`limit` parameter) | `paging.{enabled, default_limit, max_limit}` | OpenAPI query parameter |
200
206
 
201
207
  Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example. Omit all of these fields and the output keeps the standard OGC house style.
202
208
 
203
- > The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
209
+ > The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, `suppress_section_divider_pages`, `inline_section_headers`, `suppress_bibliography_indent`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`, the `sections` mode, `fmt-title`, and `bibitem-normative-style`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
204
210
 
205
211
  ---
206
212
 
@@ -294,6 +300,7 @@ abstract_tests: []
294
300
  | `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
295
301
  | `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
296
302
  | `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
303
+ | `paging` | object | no | Features `/items` paging — adds a validated `limit` query parameter to the generated OpenAPI (see below) |
297
304
  | `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
298
305
 
299
306
  ---
@@ -577,6 +584,21 @@ Generates: `/processes/compute-difference`, `/processes/compute-difference/execu
577
584
 
578
585
  ---
579
586
 
587
+ ### `paging`
588
+
589
+ Controls Features `/items` paging in the generated OpenAPI. When enabled (the default), a validated `limit` query parameter is added to every `/items` endpoint (collection-level and instance-level).
590
+
591
+ ```yaml
592
+ paging:
593
+ enabled: true # default true; set false to omit the limit parameter
594
+ default_limit: 10 # OpenAPI schema default for limit (default 10)
595
+ max_limit: 100 # OpenAPI schema maximum for limit (default 10000)
596
+ ```
597
+
598
+ The emitted parameter is `limit` (`in: query`, `type: integer`, `minimum: 1`, `maximum: <max_limit>`, `default: <default_limit>`).
599
+
600
+ ---
601
+
580
602
  ### `pubsub`
581
603
 
582
604
  When present, generates `asyncapi.yaml` alongside `openapi.yaml`.
@@ -622,6 +644,8 @@ document_metadata:
622
644
  submission_date: "2026-05-01" # → :received-date:
623
645
  approval_date: "2026-06-15" # → :issued-date:
624
646
  publication_date: "2026-07-01" # → :published-date:
647
+ doc_pub_date: "2026-07-16" # optional; overrides publication_date for the cover
648
+ # (long-format "16 July 2026") and :published-date:
625
649
  # Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
626
650
  # legal notice itself is generated by Metanorma from doc_type/status.
627
651
  notice: >
@@ -660,6 +684,12 @@ document_metadata:
660
684
  font_bold: "Source Sans Pro" # optional bold face; derived from font_regular when unset
661
685
  font_italic: "Source Sans Pro" # optional italic face for the tagline
662
686
  watermark: "DRAFT" # optional diagonal watermark stamped on the cover
687
+ # --- cover layout (all optional) ---
688
+ logo_width: 200 # target logo width in px (default 420)
689
+ logo_y: 100 # logo vertical offset from top in px (default 180)
690
+ tagline_font_size: 20 # tagline point size (default 30)
691
+ title_font_size: 40 # title point size (default 54)
692
+ bold_edition: true # render the "Edition X" line in bold (default false)
663
693
  colors:
664
694
  text: "#000000" # body text
665
695
  cover_text: "#1F3864" # cover text / section numbers / ToC
@@ -716,6 +746,23 @@ With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_
716
746
 
717
747
  > These design-element overrides (and the `page_watermark` footer overlay) reach into the OGC Metanorma flavor's XSL template names and are coupled to that flavor; a future metanorma-ogc release could require refreshing them.
718
748
 
749
+ #### Structural layout changes (divider pages, inline headers, references)
750
+
751
+ `suppress_design_elements` only changes *decorative* styling — it keeps the document's structure (standalone section-divider pages, the standard heading layout, and the reference hanging indent) intact. The following are **independent, opt-in** structural changes, so the default OGC output is unaffected unless you set them:
752
+
753
+ - **`suppress_section_divider_pages: true`** — removes the standalone full-page section dividers, so each section's content follows its heading directly.
754
+ - **`inline_section_headers: true`** — renders Level 1/2 headings inline with the title (e.g. `1 Scope`, `i Abstract`) rather than the OGC circled-number layout.
755
+ - **`suppress_bibliography_indent: true`** — flattens both the Normative References and the Bibliography so each entry is a single flush paragraph, removing the hanging indent that placed the title under the authors.
756
+
757
+ ```yaml
758
+ document_metadata:
759
+ suppress_section_divider_pages: true
760
+ inline_section_headers: true
761
+ suppress_bibliography_indent: true
762
+ ```
763
+
764
+ Additionally, when `copyright_holder` is set to a non-OGC organisation, the flavor's auto-generated "Submitting Organizations" sentence ("…submitted this Document to the Open Geospatial Consortium (OGC)") is rebranded to name that organisation instead, so no residual OGC text remains in the preface.
765
+
719
766
  #### Extending Terms and Definitions
720
767
 
721
768
  Add profile-specific terms (e.g. EDR Part 3 or Pub/Sub terms) appended after the base terms in the Terms and Definitions section:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oapi-profile-builder"
7
- version = "3.3.0"
7
+ version = "3.4.1"
8
8
  description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -173,12 +173,12 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
173
173
  draw.text(((_PAGE_W - tw) / 2, y), text, font=font, fill=fill)
174
174
  return y + th
175
175
 
176
- y = 180
176
+ y = getattr(cover, "logo_y", 180)
177
177
 
178
178
  # --- Logo (centered, scaled to a target width) ---
179
179
  logo = Image.open(logo_path).convert("RGBA")
180
- target_logo_w = 420 # desired cover logo width
181
- max_logo_w = 520 # hard cap
180
+ target_logo_w = getattr(cover, "logo_width", 420)
181
+ max_logo_w = max(520, target_logo_w)
182
182
  max_upscale = 3.0 # don't upscale tiny logos beyond this (avoids heavy blur)
183
183
  desired = min(target_logo_w, int(logo.width * max_upscale), max_logo_w)
184
184
  if logo.width != desired:
@@ -190,7 +190,8 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
190
190
 
191
191
  # --- Tagline (italic) ---
192
192
  if cover.tagline:
193
- y = centered(cover.tagline, fonts.italic_font(30), y, fg) + 60
193
+ tagline_fs = getattr(cover, "tagline_font_size", 30)
194
+ y = centered(cover.tagline, fonts.italic_font(tagline_fs), y, fg) + 60
194
195
 
195
196
  # --- Document number ---
196
197
  if m and m.doc_number:
@@ -199,16 +200,19 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
199
200
  # --- Edition ---
200
201
  edition = getattr(profile, "version", None)
201
202
  if edition:
202
- y = centered(f"Edition {edition}", fonts.regular_font(28), y, fg) + 24
203
+ bold_ed = bool(getattr(cover, "bold_edition", False))
204
+ y = centered(f"Edition {edition}", fonts.regular_font(28, bold=bold_ed), y, fg) + 24
203
205
 
204
206
  # --- Date ---
205
- date_str = _format_date(m.publication_date if m else None)
207
+ pub_date = (m.doc_pub_date or m.publication_date) if m else None
208
+ date_str = _format_date(pub_date)
206
209
  if date_str:
207
210
  y = centered(f"Dated {date_str}", fonts.regular_font(28), y, fg) + 80
208
211
 
209
212
  # --- Title (large, bold, wrapped) ---
210
213
  title = getattr(profile, "title", "") or ""
211
- title_font = fonts.regular_font(54, bold=True)
214
+ title_fs = getattr(cover, "title_font_size", 54)
215
+ title_font = fonts.regular_font(title_fs, bold=True)
212
216
  for line in _wrap(draw, title, title_font, _PAGE_W - 240):
213
217
  y = centered(line, title_font, y, fg) + 16
214
218
 
@@ -761,6 +761,35 @@ def _params_with_default_f(params: list[dict], profile: "ServiceProfile | None",
761
761
  return [f_param if p is _F else p for p in params]
762
762
 
763
763
 
764
+ def _paging_limit_param(profile: "ServiceProfile | None") -> dict | None:
765
+ """Return the `limit` query parameter for feature paging, or None when disabled."""
766
+ if not (profile and profile.paging and profile.paging.enabled):
767
+ return None
768
+ return {
769
+ "name": "limit",
770
+ "in": "query",
771
+ "required": False,
772
+ "description": "Limits the number of features returned in the response.",
773
+ "schema": {
774
+ "type": "integer",
775
+ "minimum": 1,
776
+ "maximum": profile.paging.max_limit,
777
+ "default": profile.paging.default_limit,
778
+ },
779
+ }
780
+
781
+
782
+ def _with_paging_limit(params: list[dict], profile: "ServiceProfile | None") -> list[dict]:
783
+ """Append the paging `limit` param to a copy of *params* if enabled and absent."""
784
+ limit_param = _paging_limit_param(profile)
785
+ if limit_param is None:
786
+ return params
787
+ out = list(params)
788
+ if not any(isinstance(p, dict) and p.get("name") == "limit" for p in out):
789
+ out.append(limit_param)
790
+ return out
791
+
792
+
764
793
  def _collection_paths(coll: Collection, examples: dict | None = None,
765
794
  profile: ServiceProfile | None = None) -> dict:
766
795
  paths: dict = {}
@@ -793,12 +822,14 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
793
822
  }}
794
823
 
795
824
  # Always generate Features paths (items, queryables, schema)
825
+ items_params = _with_paging_limit([_F, _LANG], profile)
826
+
796
827
  paths[f"{base}/items"] = {"get": {
797
828
  "summary": f"Get {coll.title or coll.id} items",
798
829
  "description": desc,
799
830
  "operationId": _operation_id("get", coll.id, "Features"),
800
831
  "tags": [tag],
801
- "parameters": [_F, _LANG],
832
+ "parameters": items_params,
802
833
  "responses": {
803
834
  "200": _R200_FEATURES,
804
835
  "400": _ERR_400, "404": _ERR_404, "500": _ERR_500,
@@ -863,6 +894,8 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
863
894
  # instance-level query sub-paths — GET + optional POST
864
895
  for sub_qt in (active - {"instances"}):
865
896
  sub_params = _params_with_default_f(_QUERY_PARAMS.get(sub_qt, []), profile, _vars(sub_qt))
897
+ if sub_qt == "items":
898
+ sub_params = _with_paging_limit(sub_params, profile)
866
899
  get_op = {
867
900
  "summary": f"query {coll.id} instance by {sub_qt}",
868
901
  "description": desc,
@@ -877,12 +910,13 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
877
910
  paths[f"{base}/instances/{{instanceId}}/{sub_qt}"] = path_ops
878
911
 
879
912
  elif qt == "items":
913
+ items_params = _with_paging_limit(params, profile)
880
914
  get_op = {
881
915
  "summary": f"query {coll.id} by items",
882
916
  "description": desc,
883
917
  "operationId": _operation_id("queryItems", coll.id),
884
918
  "tags": [tag],
885
- "parameters": params,
919
+ "parameters": items_params,
886
920
  "responses": cov_resp,
887
921
  }
888
922
  path_ops = {"get": get_op}
@@ -1376,6 +1410,135 @@ def _individual_test_adoc(profile: ServiceProfile, test_id: str) -> str:
1376
1410
 
1377
1411
  _PDF_OVERRIDE_FILENAME = "pdf_override.xsl"
1378
1412
 
1413
+ _OVERRIDE_SUPPRESS_SECTION_DIVIDERS = """
1414
+ <!-- Suppress section-divider pages by rendering only the main content flow. -->
1415
+ <xsl:template match="node()" mode="sections">
1416
+ <xsl:param name="num"/>
1417
+ <xsl:param name="initial-page-number"/>
1418
+ <fo:page-sequence xsl:use-attribute-sets="page-sequence-main">
1419
+ <xsl:call-template name="refine_page-sequence-main"/>
1420
+ <xsl:call-template name="insertFootnoteSeparator"/>
1421
+ <xsl:call-template name="insertHeaderFooter">
1422
+ <xsl:with-param name="num" select="$num"/>
1423
+ </xsl:call-template>
1424
+ <fo:flow flow-name="xsl-region-body">
1425
+ <fo:block line-height="125%">
1426
+ <xsl:choose>
1427
+ <xsl:when test=".//mn:indexsect">
1428
+ <xsl:apply-templates select=".//mn:indexsect" mode="index"/>
1429
+ </xsl:when>
1430
+ <xsl:otherwise>
1431
+ <xsl:apply-templates/>
1432
+ </xsl:otherwise>
1433
+ </xsl:choose>
1434
+ </fo:block>
1435
+ </fo:flow>
1436
+ </fo:page-sequence>
1437
+ </xsl:template>
1438
+ """
1439
+
1440
+ _OVERRIDE_FLATTEN_BIBLIOGRAPHY = """
1441
+ <!-- Remove the hanging indent on Normative References: the OGC flavor uses
1442
+ start-indent 25mm / text-indent -25mm so continuation lines (the title)
1443
+ sit indented under the authors. Flatten both to 0 so every line aligns at
1444
+ the left margin. -->
1445
+ <xsl:attribute-set name="bibitem-normative-style">
1446
+ <xsl:attribute name="margin-bottom">12pt</xsl:attribute>
1447
+ <xsl:attribute name="start-indent">0mm</xsl:attribute>
1448
+ <xsl:attribute name="text-indent">0mm</xsl:attribute>
1449
+ <xsl:attribute name="line-height">115%</xsl:attribute>
1450
+ </xsl:attribute-set>
1451
+
1452
+ <!-- Same for the non-normative Bibliography section: render the citation tag
1453
+ inline with the title as one flush block (wrapped lines align at the left
1454
+ margin instead of being indented under the authors). -->
1455
+ <xsl:template match="mn:references[not(@normative='true')]/mn:bibitem" name="bibitem_non_normative" priority="2">
1456
+ <xsl:param name="skip"/>
1457
+ <xsl:call-template name="setNamedDestination"/>
1458
+ <fo:block id="{@id}" margin-bottom="12pt" role="SKIP">
1459
+ <fo:inline role="SKIP">
1460
+ <xsl:apply-templates select="mn:biblio-tag">
1461
+ <xsl:with-param name="biblio_tag_part">first</xsl:with-param>
1462
+ </xsl:apply-templates>
1463
+ </fo:inline>
1464
+ <xsl:text> </xsl:text>
1465
+ <xsl:call-template name="processBibitem">
1466
+ <xsl:with-param name="biblio_tag_part">last</xsl:with-param>
1467
+ </xsl:call-template>
1468
+ </fo:block>
1469
+ </xsl:template>
1470
+ """
1471
+
1472
+ _OVERRIDE_INLINE_SECTION_HEADERS = """
1473
+ <!-- Inline Level 1 section titles (e.g. '1. Scope' in a single block, no circles, no table). -->
1474
+ <xsl:template match="mn:fmt-title" name="title">
1475
+ <xsl:variable name="level"><xsl:call-template name="getLevel"/></xsl:variable>
1476
+ <xsl:variable name="element-name">
1477
+ <xsl:choose>
1478
+ <xsl:when test="../@inline-header = 'true'">fo:inline</xsl:when>
1479
+ <xsl:otherwise>fo:block</xsl:otherwise>
1480
+ </xsl:choose>
1481
+ </xsl:variable>
1482
+ <xsl:call-template name="setNamedDestination"/>
1483
+ <xsl:variable name="title_styles">
1484
+ <styles xsl:use-attribute-sets="title-style">
1485
+ <xsl:call-template name="refine_title-style"/>
1486
+ </styles>
1487
+ </xsl:variable>
1488
+
1489
+ <xsl:choose>
1490
+ <xsl:when test="$level = 1">
1491
+ <fo:block>
1492
+ <xsl:copy-of select="xalan:nodeset($title_styles)/styles/@*[local-name() = 'space-before' or local-name() = 'margin-bottom' or local-name() = 'keep-with-next' or local-name() = 'role']"/>
1493
+ <xsl:call-template name="setIDforNamedDestinationInline"/>
1494
+ <xsl:variable name="title">
1495
+ <xsl:choose>
1496
+ <xsl:when test="mn:tab">
1497
+ <xsl:copy-of select="mn:tab[1]/following-sibling::node()"/>
1498
+ </xsl:when>
1499
+ <xsl:otherwise><xsl:copy-of select="."/></xsl:otherwise>
1500
+ </xsl:choose>
1501
+ </xsl:variable>
1502
+ <xsl:variable name="section" select="mn:tab[1]/preceding-sibling::node()"/>
1503
+ <xsl:call-template name="insertSectionTitle">
1504
+ <xsl:with-param name="section" select="$section"/>
1505
+ <xsl:with-param name="title" select="$title"/>
1506
+ <xsl:with-param name="level" select="$level"/>
1507
+ </xsl:call-template>
1508
+ </fo:block>
1509
+ </xsl:when>
1510
+ <xsl:when test="$level = 2">
1511
+ <fo:block>
1512
+ <xsl:copy-of select="xalan:nodeset($title_styles)/styles/@*[local-name() = 'space-before' or local-name() = 'margin-bottom' or local-name() = 'keep-with-next' or local-name() = 'role']"/>
1513
+ <xsl:call-template name="setIDforNamedDestinationInline"/>
1514
+ <xsl:variable name="title">
1515
+ <xsl:choose>
1516
+ <xsl:when test="mn:tab">
1517
+ <xsl:copy-of select="mn:tab[1]/following-sibling::node()"/>
1518
+ </xsl:when>
1519
+ <xsl:otherwise><xsl:copy-of select="."/></xsl:otherwise>
1520
+ </xsl:choose>
1521
+ </xsl:variable>
1522
+ <xsl:variable name="section" select="mn:tab[1]/preceding-sibling::node()"/>
1523
+ <xsl:call-template name="insertSectionTitle">
1524
+ <xsl:with-param name="section" select="$section"/>
1525
+ <xsl:with-param name="title" select="$title"/>
1526
+ <xsl:with-param name="level" select="$level"/>
1527
+ </xsl:call-template>
1528
+ </fo:block>
1529
+ </xsl:when>
1530
+ <xsl:otherwise>
1531
+ <xsl:element name="{$element-name}">
1532
+ <xsl:copy-of select="xalan:nodeset($title_styles)/styles/@*"/>
1533
+ <xsl:call-template name="setIDforNamedDestinationInline"/>
1534
+ <xsl:apply-templates/>
1535
+ <xsl:apply-templates select="following-sibling::*[1][self::mn:variant-title][@type = 'sub']" mode="subtitle"/>
1536
+ </xsl:element>
1537
+ </xsl:otherwise>
1538
+ </xsl:choose>
1539
+ </xsl:template>
1540
+ """
1541
+
1379
1542
  # Individual template overrides for the OGC Metanorma flavor's PDF XSL. Each is
1380
1543
  # keyed to a real template name in metanorma-ogc's ogc.standard.xsl and merged
1381
1544
  # in via :pdf-stylesheet-override:. These are coupled to the OGC flavor.
@@ -1519,10 +1682,19 @@ def _effective_pdf_config(m) -> dict:
1519
1682
  (default white) background with dark, readable numbers/titles so they no
1520
1683
  longer carry the OGC navy house style.
1521
1684
  """
1685
+ # suppress_design_elements ("debrand") controls only the OGC *decorative*
1686
+ # motifs — the blue crossing lines, the circular clause-number badges and the
1687
+ # short rule under titles — plus the section-divider page recolouring. It does
1688
+ # NOT remove the section-divider pages, inline the headers, or reflow the
1689
+ # bibliography: those are structural/layout choices left as independent opt-ins
1690
+ # so the default OGC document keeps its divider pages and standard layout.
1522
1691
  debrand = bool(getattr(m, "suppress_design_elements", False))
1523
1692
  crossing = debrand or bool(getattr(m, "suppress_crossing_lines", False))
1524
1693
  underlines = debrand or bool(getattr(m, "suppress_title_underlines", False))
1525
1694
  plain_nums = debrand or bool(getattr(m, "plain_section_numbers", False))
1695
+ suppress_divs = bool(getattr(m, "suppress_section_divider_pages", False))
1696
+ inline_headers = bool(getattr(m, "inline_section_headers", False))
1697
+ biblio_indent = bool(getattr(m, "suppress_bibliography_indent", False))
1526
1698
 
1527
1699
  colors = getattr(m, "colors", None)
1528
1700
  page_bg = getattr(colors, "page_background", None) if colors else None
@@ -1548,6 +1720,9 @@ def _effective_pdf_config(m) -> dict:
1548
1720
  "crossing": crossing,
1549
1721
  "underlines": underlines,
1550
1722
  "plain_nums": plain_nums,
1723
+ "suppress_divs": suppress_divs,
1724
+ "inline_headers": inline_headers,
1725
+ "biblio_indent": biblio_indent,
1551
1726
  "body_font": getattr(m, "body_font", None),
1552
1727
  "watermark": getattr(m, "page_watermark", None),
1553
1728
  "divider_bg": divider_bg,
@@ -1555,6 +1730,32 @@ def _effective_pdf_config(m) -> dict:
1555
1730
  }
1556
1731
 
1557
1732
 
1733
+ def _submitting_orgs_text_override(org_name: str) -> str:
1734
+ """Rebrand the OGC flavor's auto-generated 'Submitting Organizations' sentence.
1735
+
1736
+ The OGC Metanorma flavor hardcodes "...submitted this Document to the Open
1737
+ Geospatial Consortium (OGC):" in its presentation stage (not the i18n YAML),
1738
+ so it lands in the presentation XML as the text of the
1739
+ ``clause[@type='submitting_orgs']`` paragraph. We replace just that text node
1740
+ with the rebranded SDO name, leaving the paragraph's styling intact.
1741
+
1742
+ Fragile by nature: coupled to the exact sentence the OGC flavor emits. If the
1743
+ flavor changes that wording, this literal will no longer match and should be
1744
+ updated.
1745
+ """
1746
+ org = _xml_escape(org_name)
1747
+ return f"""
1748
+ <!-- Rebrand the flavor's auto-generated 'Submitting Organizations' sentence
1749
+ (the OGC flavor injects 'the Open Geospatial Consortium (OGC)' at its
1750
+ presentation stage). Match the clause's paragraph element specifically —
1751
+ more specific than the base mn:p template, so mn2pdf's merge appends this
1752
+ and it wins for that paragraph only, leaving all other paragraphs alone. -->
1753
+ <xsl:template match="mn:clause[@type='submitting_orgs']/mn:p">
1754
+ <fo:block xsl:use-attribute-sets="p-style" role="P">The following organizations submitted this Document to {org}:</fo:block>
1755
+ </xsl:template>
1756
+ """
1757
+
1758
+
1558
1759
  def _pdf_override_flags(m) -> list[str]:
1559
1760
  """Return the override snippets enabled by document_metadata, in order."""
1560
1761
  if not m:
@@ -1569,6 +1770,17 @@ def _pdf_override_flags(m) -> list[str]:
1569
1770
  parts.append(_OVERRIDE_SUPPRESS_TITLE_UNDERLINES)
1570
1771
  if cfg["plain_nums"]:
1571
1772
  parts.append(_plain_section_numbers_override(cfg["divider_fg"]))
1773
+ if cfg["suppress_divs"]:
1774
+ parts.append(_OVERRIDE_SUPPRESS_SECTION_DIVIDERS)
1775
+ if cfg["inline_headers"]:
1776
+ parts.append(_OVERRIDE_INLINE_SECTION_HEADERS)
1777
+ if cfg["biblio_indent"]:
1778
+ parts.append(_OVERRIDE_FLATTEN_BIBLIOGRAPHY)
1779
+ # Rebrand the flavor's auto-generated "Submitting Organizations" sentence to
1780
+ # name the copyright holder (e.g. DGIWG) instead of OGC, when rebranding.
1781
+ holder = getattr(m, "copyright_holder", None)
1782
+ if holder and holder.strip().upper() not in ("OGC", "OPEN GEOSPATIAL CONSORTIUM"):
1783
+ parts.append(_submitting_orgs_text_override(holder))
1572
1784
  # When the divider background is rebranded (light), force the big title
1573
1785
  # colour too — the flavor inherits white text, invisible on a light page.
1574
1786
  if cfg["divider_bg"] is not None:
@@ -1699,7 +1911,7 @@ def _build_document_adoc(profile: ServiceProfile) -> str:
1699
1911
  ":committee: technical",
1700
1912
  f":docnumber: {m.doc_number if m else profile.name}",
1701
1913
  f":copyright-year: {year}",
1702
- f":published-date: {(m.publication_date if m and m.publication_date else default_date)}",
1914
+ f":published-date: {((m.doc_pub_date or m.publication_date) if m and (m.doc_pub_date or m.publication_date) else default_date)}",
1703
1915
  f":issued-date: {(m.approval_date if m and m.approval_date else default_date)}",
1704
1916
  f":received-date: {(m.submission_date if m and m.submission_date else default_date)}",
1705
1917
  ]
@@ -472,6 +472,11 @@ class CoverPage(BaseModel):
472
472
  "Note: this marks the cover only; an every-page watermark requires the Metanorma layer."
473
473
  ),
474
474
  )
475
+ logo_width: int = Field(default=420, description="Target logo width on cover page")
476
+ logo_y: int = Field(default=180, description="Logo y-offset from top of cover page")
477
+ tagline_font_size: int = Field(default=30, description="Font size of cover page tagline")
478
+ title_font_size: int = Field(default=54, description="Font size of cover page title")
479
+ bold_edition: bool = Field(default=False, description="When true, render edition in bold font")
475
480
 
476
481
  @field_validator("background", "text_color")
477
482
  @classmethod
@@ -599,6 +604,18 @@ class DocumentMetadata(BaseModel):
599
604
  "the flavor's built-in text via :boilerplate-authority:."
600
605
  ),
601
606
  )
607
+ doc_pub_date: str | None = Field(default=None, description="Alternative field for publication date (YYYY-MM-DD)")
608
+ suppress_section_divider_pages: bool = Field(default=False, description="When true, completely remove section-divider pages from the PDF")
609
+ inline_section_headers: bool = Field(default=False, description="When true, render Level 1 headings inline without circles or table formatting")
610
+ suppress_bibliography_indent: bool = Field(
611
+ default=False,
612
+ description=(
613
+ "When true, remove the OGC flavor's hanging indent on non-normative "
614
+ "bibliography/reference entries so the citation tag and title render as a "
615
+ "single flush paragraph (wrapped lines align at the left margin instead of "
616
+ "being indented under the authors). Coupled to the OGC Metanorma flavor."
617
+ ),
618
+ )
602
619
  suppress_flavor_logo: bool = Field(
603
620
  default=False,
604
621
  description=(
@@ -651,10 +668,15 @@ class DocumentMetadata(BaseModel):
651
668
  default=False,
652
669
  description=(
653
670
  "When true, strip the OGC flavor's decorative PDF design elements — the "
654
- "blue 'crossing lines' motif, the circular badges around clause numbers, and "
655
- "the short rule under clause titles — via a targeted PDF stylesheet override. "
656
- "Produces a plainer 'i. Abstract' / '1 Scope' heading style for SDOs (e.g. "
657
- "DGIWG) that don't use the OGC house style. Coupled to the OGC Metanorma flavor."
671
+ "blue 'crossing lines' motif, the circular badges around clause numbers, "
672
+ "and the short rule under clause titles — via a targeted PDF stylesheet "
673
+ "override, and recolour the section-divider pages to a clean light "
674
+ "background for SDOs (e.g. DGIWG) that don't use the OGC house style. "
675
+ "This affects only decorative styling: it does NOT remove the "
676
+ "section-divider pages, inline the section headers, or reflow the "
677
+ "bibliography — use suppress_section_divider_pages, inline_section_headers "
678
+ "and suppress_bibliography_indent for those structural changes. Coupled to "
679
+ "the OGC Metanorma flavor."
658
680
  ),
659
681
  )
660
682
  page_watermark: str | None = Field(
@@ -666,7 +688,7 @@ class DocumentMetadata(BaseModel):
666
688
  ),
667
689
  )
668
690
 
669
- @field_validator("submission_date", "approval_date", "publication_date")
691
+ @field_validator("submission_date", "approval_date", "publication_date", "doc_pub_date")
670
692
  @classmethod
671
693
  def _valid_date(cls, v: str | None) -> str | None:
672
694
  if v is not None and not _DATE_RE.match(v):
@@ -700,6 +722,13 @@ class PubSubConfig(BaseModel):
700
722
  collection_filters: dict[str, CollectionPubSub] = Field(default_factory=dict, description="Per-collection filter overrides")
701
723
 
702
724
 
725
+ class PagingConfig(BaseModel):
726
+ """Optional pagination parameters config."""
727
+ enabled: bool = Field(default=True, description="When true, include standard limit parameter in /items endpoint")
728
+ default_limit: int = Field(default=10, description="Default number of features to return", ge=1)
729
+ max_limit: int = Field(default=10000, description="Maximum allowed limit for features returned", ge=1)
730
+
731
+
703
732
  # ---------------------------------------------------------------------------
704
733
  # Root model — the authoritative profile definition
705
734
  # ---------------------------------------------------------------------------
@@ -767,6 +796,7 @@ class ServiceProfile(BaseModel):
767
796
  pubsub: PubSubConfig | None = None
768
797
  processes: list[dict] = Field(default_factory=list)
769
798
  document_metadata: DocumentMetadata | None = None
799
+ paging: PagingConfig = Field(default_factory=PagingConfig)
770
800
 
771
801
  # OGC API - EDR Part 3 specific fields
772
802
  required_conformance_classes: list[str] = Field(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oapi-profile-builder
3
- Version: 3.3.0
3
+ Version: 3.4.1
4
4
  Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
5
5
  Author-email: Shane Mill <shane.mill@noaa.gov>
6
6
  License: Apache License
@@ -239,15 +239,21 @@ Everything about the document — cover, title-page metadata, legal page, footer
239
239
  | PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
240
240
  | Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
241
241
  | Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
242
+ | Cover layout (logo size/position, tagline/title size, bold edition) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, bold_edition}` | Generated cover image |
243
+ | Cover date | `document_metadata.doc_pub_date` | Long-format date on the generated cover + `:published-date:` |
242
244
  | Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
243
245
  | Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
246
+ | Remove the standalone section-divider pages | `document_metadata.suppress_section_divider_pages` | `:pdf-stylesheet-override:` |
247
+ | Inline section headings (`1 Scope` in one line, no circle) | `document_metadata.inline_section_headers` | `:pdf-stylesheet-override:` |
248
+ | Flatten reference hanging indent (title under authors) | `document_metadata.suppress_bibliography_indent` | `:pdf-stylesheet-override:` |
244
249
  | Every-page DRAFT-style watermark (over content) | `document_metadata.page_watermark` | `:pdf-stylesheet-override:` (footer overlay) |
245
250
  | Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
246
251
  | Submitter table, notice paragraph, normative refs, extra terms | `document_metadata.{submitters, notice, normative_references, terms}` | Metanorma attributes / sections |
252
+ | Features `/items` paging (`limit` parameter) | `paging.{enabled, default_limit, max_limit}` | OpenAPI query parameter |
247
253
 
248
254
  Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example. Omit all of these fields and the output keeps the standard OGC house style.
249
255
 
250
- > The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
256
+ > The `suppress_flavor_logo`, `suppress_design_elements`/`suppress_*`, `suppress_section_divider_pages`, `inline_section_headers`, `suppress_bibliography_indent`, and `page_watermark` overrides reach into the OGC Metanorma flavor's XSL templates (e.g. `insertLogoPreface`, `insertCrossingLines`, `insertSectionNumInCircle`, `insertFooter`, the `sections` mode, `fmt-title`, and `bibitem-normative-style`) and are coupled to that flavor. Everything else uses documented Metanorma attributes. mn2pdf merges our same-named templates over the flavor's at equal precedence (via `merge_override.xsl`), which is what makes these work; a future metanorma-ogc release could require refreshing the template names. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
251
257
 
252
258
  ---
253
259
 
@@ -341,6 +347,7 @@ abstract_tests: []
341
347
  | `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
342
348
  | `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
343
349
  | `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
350
+ | `paging` | object | no | Features `/items` paging — adds a validated `limit` query parameter to the generated OpenAPI (see below) |
344
351
  | `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
345
352
 
346
353
  ---
@@ -624,6 +631,21 @@ Generates: `/processes/compute-difference`, `/processes/compute-difference/execu
624
631
 
625
632
  ---
626
633
 
634
+ ### `paging`
635
+
636
+ Controls Features `/items` paging in the generated OpenAPI. When enabled (the default), a validated `limit` query parameter is added to every `/items` endpoint (collection-level and instance-level).
637
+
638
+ ```yaml
639
+ paging:
640
+ enabled: true # default true; set false to omit the limit parameter
641
+ default_limit: 10 # OpenAPI schema default for limit (default 10)
642
+ max_limit: 100 # OpenAPI schema maximum for limit (default 10000)
643
+ ```
644
+
645
+ The emitted parameter is `limit` (`in: query`, `type: integer`, `minimum: 1`, `maximum: <max_limit>`, `default: <default_limit>`).
646
+
647
+ ---
648
+
627
649
  ### `pubsub`
628
650
 
629
651
  When present, generates `asyncapi.yaml` alongside `openapi.yaml`.
@@ -669,6 +691,8 @@ document_metadata:
669
691
  submission_date: "2026-05-01" # → :received-date:
670
692
  approval_date: "2026-06-15" # → :issued-date:
671
693
  publication_date: "2026-07-01" # → :published-date:
694
+ doc_pub_date: "2026-07-16" # optional; overrides publication_date for the cover
695
+ # (long-format "16 July 2026") and :published-date:
672
696
  # Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
673
697
  # legal notice itself is generated by Metanorma from doc_type/status.
674
698
  notice: >
@@ -707,6 +731,12 @@ document_metadata:
707
731
  font_bold: "Source Sans Pro" # optional bold face; derived from font_regular when unset
708
732
  font_italic: "Source Sans Pro" # optional italic face for the tagline
709
733
  watermark: "DRAFT" # optional diagonal watermark stamped on the cover
734
+ # --- cover layout (all optional) ---
735
+ logo_width: 200 # target logo width in px (default 420)
736
+ logo_y: 100 # logo vertical offset from top in px (default 180)
737
+ tagline_font_size: 20 # tagline point size (default 30)
738
+ title_font_size: 40 # title point size (default 54)
739
+ bold_edition: true # render the "Edition X" line in bold (default false)
710
740
  colors:
711
741
  text: "#000000" # body text
712
742
  cover_text: "#1F3864" # cover text / section numbers / ToC
@@ -763,6 +793,23 @@ With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_
763
793
 
764
794
  > These design-element overrides (and the `page_watermark` footer overlay) reach into the OGC Metanorma flavor's XSL template names and are coupled to that flavor; a future metanorma-ogc release could require refreshing them.
765
795
 
796
+ #### Structural layout changes (divider pages, inline headers, references)
797
+
798
+ `suppress_design_elements` only changes *decorative* styling — it keeps the document's structure (standalone section-divider pages, the standard heading layout, and the reference hanging indent) intact. The following are **independent, opt-in** structural changes, so the default OGC output is unaffected unless you set them:
799
+
800
+ - **`suppress_section_divider_pages: true`** — removes the standalone full-page section dividers, so each section's content follows its heading directly.
801
+ - **`inline_section_headers: true`** — renders Level 1/2 headings inline with the title (e.g. `1 Scope`, `i Abstract`) rather than the OGC circled-number layout.
802
+ - **`suppress_bibliography_indent: true`** — flattens both the Normative References and the Bibliography so each entry is a single flush paragraph, removing the hanging indent that placed the title under the authors.
803
+
804
+ ```yaml
805
+ document_metadata:
806
+ suppress_section_divider_pages: true
807
+ inline_section_headers: true
808
+ suppress_bibliography_indent: true
809
+ ```
810
+
811
+ Additionally, when `copyright_holder` is set to a non-OGC organisation, the flavor's auto-generated "Submitting Organizations" sentence ("…submitted this Document to the Open Geospatial Consortium (OGC)") is rebranded to name that organisation instead, so no residual OGC text remains in the preface.
812
+
766
813
  #### Extending Terms and Definitions
767
814
 
768
815
  Add profile-specific terms (e.g. EDR Part 3 or Pub/Sub terms) appended after the base terms in the Terms and Definitions section: