cabo / kramdown-rfc

An XML2RFC (RFC799x) backend for Thomas Leitner's kramdown markdown parser
MIT License
195 stars 83 forks source link

better defaults for nested ordered lists (distinguish sublist numerals from parent list numerals) #227

Closed dkg closed 6 months ago

dkg commented 7 months ago

When describing an ordered sequence of steps (e.g., in pseudocode in a draft), it's nice to present them as an ordered list (e.g. 1,2,3,4) rather than unordered (just bullets). However, if there are sub-steps in such a presentation, the natural way to present them is with a nested ordered list. But the nested list restarts with same numbering scheme as the outer list, which is pretty confusing.

For example, this text:

1. bar <- Retrieve config
1. foo <- baz(bar)
1. for qux in foo:
    1. Do thing A(qux)
    1. Do thing B(qux)
1. cleanup

renders as:

   1.  bar <- Retrieve config
   2.  foo <- baz(bar)
   3.  for qux in foo:
       1.  Do thing A(qux)
       2.  Do thing B(qux)
   4.  cleanup

Here are two better ways that it could render:

   1.  bar <- Retrieve config
   2.  foo <- baz(bar)
   3.  for qux in foo:
       3.1.  Do thing A(qux)
       3.2.  Do thing B(qux)
   4.  cleanup

or

   1.  bar <- Retrieve config
   2.  foo <- baz(bar)
   3.  for qux in foo:
       a.  Do thing A(qux)
       b.  Do thing B(qux)
   4.  cleanup
cabo commented 7 months ago

The format of an ordered list can be specified by the type= attribute, please see RFC 7991: The "xml2rfc" Version 3 Vocabulary.

I think what you are asking is a way for the outer list to be parametrized to supply a type= attribute for any inner list, so it does not have to be repeated for each inner list. It would be easy to add something like type="a" (your second "better way"). Doing something for your first "better way" would require kramdown-rfc to generate the label on the outer list at each point an inner list is encountered (here: something like type="3.%d.", where the 3. is computed from the structure of the outer list, and values of type=, group=, and start=). Second-guessing xml2rfc to this extent is a bit beyond kramdown-rfc's remit. But I can certainly think about helping the second "better way" with pseudo-attributes of the form sub-type, sub-sub-type etc. that get inherited as type and sub-type to the next inner list.

dkg commented 7 months ago

@cabo if there is a way to do that from kramdown-rfc, that would be great. You mention it as a way to parameterize the outer list itself, which would definitely be useful. Even more useful (and simple, for my purposes) would be a document-wide setting that applies to all nested, ordered lists.

What i would like ideally is something in the YAML metadata block like:

nested-ordered-types: 1 a i A I

which would make the top-level ordered list type='1', and the next-level ordered list type='a', the third-level ordered list type='i', and so on.

cabo commented 7 months ago

Will the code need to consider intervening unordered lists? Or can we start at 1 again if that happens?

1. one
2. two
   - two-left
      a. two-left-a
      b. two-left-b
   - two-right
cabo commented 7 months ago
nested-ordered-types: 1 a i A I

Those types can contain spaces, so a space-separated list won't work too well. Probably needs to be

nested-ordered-types: ["1", a, i, A, I]

dkg commented 7 months ago

Will the code need to consider intervening unordered lists? Or can we start at 1 again if that happens?

I don't have much of an opinion here -- that seems pretty weird to me, if i'm doing nested ordered lists it's probably because i want to be able to refer to a particular sub-sub-*-item by its coordinates (e.g., "clause 3(b)iii", the way people talk about US legislation) , and i don't see how an intervening unordered list would fit in here.

I'd be fine with any of the following three choices:

cabo commented 7 months ago

OK, implementation-wise your number 1 (see what I did here :-) seems easiest. It is also probably easiest to roll through the array, i.e., putting the shifted off current value back in at the end for the nested items. Default then can simply be ["1"].

paulehoffman commented 7 months ago

Is there a reason why we're not trying to follow what would happen from CommonMark? CommonMark puts out HTML, but we can follow how it does levels and nesting instead of inventing our own way.

cabo commented 7 months ago

On 19. Apr 2024, at 19:33, Paul Hoffman @.***> wrote:

Is there a reason why we're not trying to follow what would happen from CommonMark?

Yes.
Kramdown-rfc is based on kramdown, which is a dialect that was formed before common mark made decisions, often incompatible with existing markdown dialects.

In the hierarchy of objectives for kramdown-rfc, stability is very high, because I-Ds are often “dusty decks” that should continue to work after periods of time that are natural in the IETF, e.g., half a decade.

Where these considerations do not get in the way (and adding a YAML control certainly doesn’t), we can innovate.

CommonMark puts out HTML, but we can follow how it does levels and nesting instead of inventing our own way.

As far as I know, commonmark doesn’t do anything useful in the area of labeling nested ordered lists, or actually even in the way of supporting different numbering styles for single-level lists.
RFCXMLv3 has type=, so we are covered by using an IAL.
What is proposed here is to add some automation that would generate this attribute.

(Commonmark does do one thing that doesn’t have to do with nesting, which is extracting start numbers — that would be a great feature to have, but unfortunately it won’t be part of kramdown 1.
Well, we do have the start= attribute in RFCXMLv3 as well, and that can be addressed with an IAL, as can be group= for continuing numbering across interruptions.

Commonmark also has a broken 2 concept of tight vs. loose lists.
RFCXMLv3 has spacing=, which again can be accessed via an IAL.)

Grüße, Carsten

cabo commented 7 months ago

I have written the code, but

https://github.com/ietf-tools/xml2rfc/issues/1122

Will push this anyway after some more testing.

cabo commented 7 months ago

Now in 1.7.9:

1.7.9 Add option nested_ol_types

Set globally in {::options nested_ol_types="1, i, a" /} (markdown, not YAML header) or for a single <ol in an IAL {: nested_ol_types="1 (%i) A"}

The value needs to be an array of <ol type= values, expressed as one of:

  1. A YAML array
  2. A string that will be split on commas (with optional blank space following)
  3. A string that will be split on blank space

When testing this, an HTML rendering issue might cause some confusion: https://github.com/ietf-tools/xml2rfc/issues/1122

cabo commented 7 months ago

1.7.10 now also takes nested_ol_types from YAML kramdown_options

As in

kramdown_options:
  nested_ol_types: 1, i, a

(overridden by ::options and by pseudo-attribute on <ol)

cabo commented 7 months ago

1.7.11 now also takes an option ol_start_at_first_marker

As in

kramdown_options:
  ol_start_at_first_marker: true

Default: false (for kramdown-rfc backward compatibility)

If true, an ordered list (<ol) will use the number in its first marker (1 for 1. etc.) as the default value of the start= attribute.

(This increases commonmark compatibility, see https://github.com/cabo/kramdown-rfc/issues/227#issuecomment-2067148163)

cabo commented 7 months ago

I think the recent changes should address both the original observation and the discussion, so please close this if this works for you. https://github.com/ietf-tools/xml2rfc/issues/1122 remains though, for now.

cabo commented 6 months ago

Nice -- a solution is on its way in https://github.com/ietf-tools/xml2rfc/pull/1123 (If you don't need uppercase roman numerals, everything else appears to work.)

dkg commented 6 months ago

This works great. Thank you, @cabo! Much appreciated.