vivliostyle/vfm

spec: Attributes on auto-generated parent elements (section, figure)

MurakamiShinyu opened this issue · 6 comments

Update 2022-10-01 開発者会議 での討論の結果、親要素への属性の移動・コピーをすべて廃止する案を採用することに決定。

よって、以下の最初の提案「自動生成の親要素に移動・コピーする属性を #id.class に限定」は取り下げ。


現在のvfmの仕様では、見出しに指定された属性は、HTMLの見出し要素(h1-h6)だけでなく自動生成されるsection要素にも出力される(ただし id は section のみなど例外あり)。また、画像でimg要素を囲むfigure要素が自動生成される場合にも同様で、指定された属性はimgとfigureの両方に出力される(ただし width 属性などは img のみ)。この仕様になったのは以下のissueで議論された結果である:

しかし、この仕様であまり便利ではないことがある。

たとえばstyle属性で見出しのスタイルを指定したい場合。

例:

## Lorem {style="border: solid; font-size: 2em"}

Lorem ipsum dolor sit amet.

出力されるHTMLは:

<section style="border: solid; font-size: 2em" id="lorem" class="level2">
  <h2 style="border: solid; font-size: 2em">Lorem</h2>
  <p>Lorem ipsum dolor sit amet.</p>
</section>

このように見出しに指定したスタイルは見出しのh2要素だけでなくsection要素にも指定されてしまい、意図しない結果になる。

通常はstyle属性ではなくクラスの指定によってスタイル指定を行うが、それでも同様の問題がある。たとえばスタイルシートで .lorem {border: solid} というクラスの定義があってこのクラスを見出しに指定した場合、見出しとセクション全体の両方がボーダー囲みになる。

このためvfmにより生成されるHTMLのためのスタイルシートを書くときは、同じクラス名が見出しとsectionの両方にあることを意識して、h2.lorem {…}section.lorem {…} のように要素名も指定してクラスのスタイルを定義しなくてはならない。クラス名だけでスタイルを指定しているような既存のスタイルシートを利用できないのも不便だ(たとえば Tailwind CSS では、 .border-solid {border: solid} のようにクラス名に対するスタイルが定義されているが、このようなスタイルシートを利用できない)。

仕様変更の提案

この問題の改善のために、以下の仕様変更を行うことを提案する。

  • 自動生成の親要素に移動・コピーする属性を #id.class に限定
    • #id の形式で指定したid属性は自動生成の親要素に移動、.class の形式で指定したclass名は親要素にコピー(これらは変更なし) ※これを変更する案もあり: 親要素への属性の移動・コピーをすべて廃止する案
    • それ以外の形式での属性の指定(name=value または値を省略した name だけの形式)は、親要素にコピーしない
  • 明示的な親要素がある場合の見出しや画像では親要素の自動生成をせず、属性の移動・コピーをしない

以下、詳しい説明。

自動生成の親要素に移動・コピーする属性を #id.class に限定

自動生成の親要素に移動・コピーする属性を #id.class に限定し、それ以外の形式での属性の指定(name=value または値を省略した name の形式)は、親要素にコピーしないようにする。

idとclassについても、id=IDclass=CLASS という形式での指定であれば、親要素ではなく当該要素のidとclassとなる。
.classclass=CLASS の両方の指定が同時にある場合は .class は親要素のクラス、class=CLASS は当該要素のクラスとなる。
これによって、親要素と当該要素にそれぞれ別のidとclassを指定することが可能になる。

例:

## Lorem {#sec01 .sec-lorem id=h01 class=h-lorem style="border: solid" lang="la"}

Lorem ipsum dolor sit amet.

現在のvfmで出力されるHTML:

<section id="sec01" class="level2 sec-lorem h-lorem" style="border: solid" lang="la">
  <h2 class="sec-lorem h-lorem" style="border: solid" lang="la">Lorem</h2>
  <p>Lorem ipsum dolor sit amet.</p>
</section>

この仕様変更をおこなった場合の出力されるHTML:

<section id="sec01" class="level2 sec-lorem">
  <h2 id="h01" class="h-lorem" style="border: solid" lang="la">Lorem</h2>
  <p>Lorem ipsum dolor sit amet.</p>
</section>

例:

![Lorem ipsum](lipsum.png){#fig01 .fig-lipsum id=img01 class=img-lipsum width=100 style="border: solid"}

現在のvfmで出力されるHTML:

<figure id="fig01" class="fig-lipsum img-lipsum" style="border: solid">
  <img src="lipsum.png" alt="Lorem ipsum" class="fig-lipsum img-lipsum" width="100" style="border: solid">
  <figcaption aria-hidden="true">Lorem ipsum</figcaption>
</figure>

この仕様変更をおこなった場合の出力されるHTML:

<figure id="fig01" class="fig-lipsum">
  <img src="lipsum.png" alt="Lorem ipsum" class="img-lipsum" width="100" style="border: solid">
  <figcaption aria-hidden="true">Lorem ipsum</figcaption>
</figure>

現在の仕様でも一部の属性(width など)は親要素にコピーしないので、変更後の仕様(name=value 形式の属性はすべてコピーしない)のほうが一貫性があって分かりやすいといえる。

注意点:

  • これまでは #ID.Class はそれぞれ id=IDclass=Class の簡易記法といえたが、この仕様変更によってそうではなくなる。
  • 複数のクラス名を指定するには、親要素にコピーするのであれば {.Class1 .Class2} と書き、そうでないなら {class="Class1 Class2"} と書くことになる。
  • 属性指定で値を省略した形式 {attr}{attr=""} と同じ意味である(HTMLの属性の仕様から)。したがって {.foo class}{.foo class=""} と同じで、親要素にのみクラス名を出力(class="foo")、当該要素はクラス名なし(class="")となる。

検討事項:

  • .class#id と同様に親要素だけに属性を出力するものとしたほうがよいかもしれない。
    • 問題点: 仕様変更の影響がより大きくなり、これまでのvfmやpandocのsection-divsからの移行がめんどうになる。
  • あるいは、親要素への属性の移動・コピーをすべて廃止する案 のほうがよいかもしれない。

明示的な親要素がある場合の見出しや画像では親要素の自動生成をせず、属性の移動・コピーをしない

これについては、別のissueに分ける:

親要素への属性の移動・コピーをすべて廃止する案

もともと、自動生成の親要素にid属性を移動したりclass属性をコピーする理由は、そうしないとCSSで自動生成されたsectionのスタイルを指定することができないためだった。しかし、CSSで :has() 擬似クラスが使えるようになると、その必要はなくなる。

:has() 擬似クラスはChrome 105から利用可能。Vivliostyle.jsでももうすぐ実装予定:

たとえば、次のようなHTMLで

<section>
  <h2 id="ID1" class="A">Aaaa</h2>
  <p>Aaaa aaa aaaaa.</p>
</section>
<section>
  <h2 id="ID2" class="B">Bbbbb</h2>
  <p>Bbbbbb bbb bbbb.</p>
</section>

このようにsection要素に属性がない場合であっても、次のように :has() を使うと各sectionのスタイルの指定ができる:

section:has(> #ID1) {
  …
}
section:has(> .B) {
  …
}

section要素にどうしてもidやclassやその他の属性を指定したい場合は、HTMLブロックで明示的にsection要素を書くとよい。その提案:

Attributesでメタデータとしてfigureを出力するかしないかを決定できるようにしてはどうでしょう。

![not-fig](./foo.png){not-fig}

result:

<img alt="not-fig" src="./foo.png">

追記:

単純にaltをAttributesで指定しても差別化によって回避できますね。

![](./foo.png){alt="not-fig"}

Update 2022-10-01 開発者会議 での討論の結果、親要素への属性の移動・コピーをすべて廃止する案を採用することに決定。

自動生成した <section> には aria-labelledby 属性を付加すること

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#labeling_section_content

Sectioning content can be labeled using a combination of the aria-labelledby and id attributes, with the label concisely describing the purpose of the section. …

この aria-labelledby 属性を自動生成した <section> に付加するとよいと思います。

例:

# Heading 1 {.one}

One.

## Heading 2 {.two}

Two.

この入力に対して、現在のvfmでは次のHTMLコードを生成します。

<section class="level1 one" id="heading-1">
  <h1 class="one">Heading 1</h1>
  <p>One.</p>
  <section class="level2 two" id="heading-2">
    <h2 class="two">Heading 2</h2>
    <p>Two.</p>
  </section>
</section>

これに対して、親要素への属性の移動・コピーをしないようにする変更と aria-labelledby 属性を付加する変更を加えると、出力されるHTMLは次のようになります:

<section class="level1" aria-labelledby="heading-1">
  <h1 class="one" id="heading-1">Heading 1</h1>
  <p>One.</p>
  <section class="level2" aria-labelledby="heading-2">
    <h2 class="two" id="heading-2">Heading 2</h2>
    <p>Two.</p>
  </section>
</section>

figure 対応により本 Issue は close 可能。

破壊的な変更なので、本 issue は v2.0.0 以降とする (Major 更新)。