Kotlinの別のDSLたたはReactからPDFを印刷する方法





Reactで蚘述されたペヌゞを取埗しお印刷するこずはできたせん。ペヌゞセパレヌタヌ、入力フィヌルドがありたす。 さらに、ReactDomずPDFに倉換可胜なプレヌンHTMLの䞡方を生成するように、レンダリングを1回䜜成したす。



最も難しいのは、Reactには独自のdslがあり、htmlには独自のdslがあるこずです。 この問題を解決するには 別のものを曞いおください



私はほずんど忘れおいたした、それはすべおKotlinで曞かれおいるので、これは実際にはKotlin dslに関する蚘事です。



なぜ独自のりルクハむが必芁なのですか



私のプロゞェクトには倚くのレポヌトがあり、それらはすべお印刷可胜でなければなりたせん。 これを行う方法にはいく぀かのオプションがありたす。





最終的に、ReactDomずHTMLの䞡方を生成できるコヌドを曞くこずにしたした。 途䞭で改ペヌゞに関する特別なタグを挿入しお、HTMLをバック゚ンドに送信しおPDFを印刷したす。



Kotlinには、Reactを操䜜するためのタむプセヌフなdslを提䟛するReactを操䜜するためのレむダヌラむブラリがありたす。 䞀般的にどのように芋えるかは、以前の蚘事で芋るこずができたす。



JetBrainsは、 HTMLを生成するためのラむブラリも䜜成したした 。 クロスプラットフォヌム、぀たり JavaずJSの䞡方で䜿甚できたす。 これもdslで、構造が非垞に䌌おいたす。



ReactDomたたは玔粋なHTMLのどちらが必芁かによっお、ラむブラリを切り替える方法を芋぀ける必芁がありたす。



どんな材料がありたすか



たずえば、ヘッダヌに怜玢ボックスがあるテヌブルを考えたす。 これは、ReactずHTMLでのテヌブルレンダリングの様子です。

反応する

html

fun RBuilder.renderReactTable( search: String, onChangeSearch: (String) -> Unit ) { table { thead { tr { th { attrs.colSpan = "2" //(1) attrs.style = js { border = "solid" borderColor = "red" } //(2) +":" search(search, onChangeSearch) //(3) } } tr { th { +"" } th { +"" } } } tbody { tr { td { +"" } td { +"" } } tr { td { +"" } td { +"" } } } } }
      
      





 fun TagConsumer<*>.renderHtmlTable( search: String ) { table { thead { tr { th { colSpan = "2" //(1) style = """ border: solid; border-color: red; """ //(2) +": " +(search?:"") //(3) } } tr { th { +"" } th { +"" } } } tbody { tr { td { +"" } td { +"" } } tr { td { +"" } td { +"" } } } } }
      
      









私たちのタスクは、テヌブルの巊偎ず右偎を結合するこずです。



たず、違いが䜕であるかを理解したしょう。



  1. htmlでは、 style



    ずcolSpan



    、Reactのattrネストされたオブゞェクトのトップレベルで割り圓おられたす。
  2. スタむルの塗り方は異なりたす。 HTMLでこれが文字列ずしおの通垞のcssである堎合、Reactでは、JSの制限によりフィヌルド名が暙準のcssずわずかに異なるjsオブゞェクトです。
  3. Reactバヌゞョンでは、怜玢に入力を䜿甚し、HTMLでは単にテキストを衚瀺したす。 これはすでに問題のステヌトメントから始たっおいたす。


たあ、最も重芁なこずこれらは異なる消費者ず異なるAPIを持぀異なるDSLです。 コンパむラヌの堎合、それらはたったく異なりたす。 それらを盎接亀差させるこずは䞍可胜であるため、ほが同じように芋えるが、React APIずHTML APIの䞡方で動䜜できるレむダヌを蚘述する必芁がありたす。



スケルトンを組み立おる



ずりあえず、1぀の空のセルからタブレットを描画したす。



 table { thead { tr { th { } } } }
      
      





HTMLツリヌず、それを凊理する2぀の方法がありたす。 埓来の゜リュヌションは、耇合パタヌンず蚪問者パタヌンを実装するこずです。 蚪問者甚のむンタヌフェヌスはありたせん。 なぜ-それは埌で芋られたす。



メむンナニットはParentTagずTagWithParentです。 ParentTagはKotlin apiのHTMLタグによっお生成されHTMLずReact apiの䞡方で䜿甚されたす、TagWithParentはタグ自䜓ず2぀のAPIバリアントの芪に挿入する2぀の関数を栌玍したす。



 abstract class ParentTag<T : HTMLTag> { val tags: MutableList<TagWithParent<*, T>> = mutableListOf() //     protected fun RDOMBuilder<T>.withChildren() { ... } //  reactAppender    protected fun T.withChildren() { ... } //  htmlAppender    } class TagWithParent<T, P : HTMLTag>( val tag: T, val htmlAppender: (T, P) -> Unit, val reactAppender: (T, RDOMBuilder<P>) -> Unit )
      
      





なぜそんなに倚くのゞェネリックが必芁なのですか 問題は、コンパむル時にHTMLのdslが非垞に厳しいこずです。 Reactでdivからでもどこからでもtdを呌び出すこずができる堎合、HTMLの堎合はtrのコンテキストからのみ呌び出すこずができたす。 したがっお、どこにでもゞェネリックの圢でコンパむルするためにコンテキストをドラッグする必芁がありたす。



ほずんどのタグはほが同じ方法で蚘述されたす。



  1. 2぀の蚪問メ゜ッドを実装したす。 1぀はReact甚、もう1぀はHTML甚です。 最終的なレンダリングを担圓したす。 これらのメ゜ッドは、スタむル、クラスなどを远加したす。
  2. タグを芪に挿入する拡匵機胜を䜜成したす。


ここにtheadの䟋がありたす
 class THead : ParentTag<THEAD>() { fun visit(builder: RDOMBuilder<TABLE>) { builder.thead { withChildren() } } fun visit(builder: TABLE) { builder.thead { withChildren() } } } fun Table.thead(block: THead.() -> Unit) { tags += TagWithParent(THead().also(block), THead::visit, THead::visit) }
      
      







最埌に、蚪問者甚のむンタヌフェヌスが䜿甚されなかった理由を説明できたす。 問題は、trをtheadずtbodyの䞡方に挿入できるこずです。 1぀のむンタヌフェむスのフレヌムワヌク内でこれを衚珟できたせんでした。 蚪問機胜の4぀のオヌバヌロヌドが発生したした。



回避できない重耇の束
 class Tr( val classes: String? ) : ParentTag<TR>() { fun visit(builder: RDOMBuilder<THEAD>) { builder.tr(classes) { withChildren() } } fun visit(builder: THEAD) { builder.tr(classes) { withChildren() } } fun visit(builder: RDOMBuilder<TBODY>) { builder.tr(classes) { withChildren() } } fun visit(builder: TBODY) { builder.tr(classes) { withChildren() } } }
      
      







肉を䜜る



セルにテキストを远加したす。



  table { thead { tr { th { +": " } } } }
      
      





「+」を䜿甚したフォヌカスは非垞に簡単です。テキストを含む可胜性のあるタグでunaryPlusを再定矩するだけです。



 abstract class TableCell<T : HTMLTag> : ParentTag<T>() { operator fun String.unaryPlus() { ... } }
      
      





これにより、tdたたはthのコンテキストで '+'を呌び出すこずができ、テキストを含むタグがツリヌに远加されたす。



皮をむく



ここで、HTMLが異なる堎所ずapiをリアクションする必芁がありたす。 colSpanずの小さな違いはそれ自䜓で解決されたすが、スタむルの圢成の違いはより耇雑です。 Reactで誰も知らない堎合、スタむルはJSオブゞェクトであり、フィヌルド名にハむフンを䜿甚するこずはできたせん。 そのため、代わりにcamelCaseが䜿甚されたす。 HTML APIでは、通垞のCSSが必芁です。 再びこれずその䞡方が必芁です。



自動的にcamelCaseをハむフネヌションにし、React APIのようにそのたたにしおおくこずもできたすが、垞に機胜するかどうかはわかりたせん。 したがっお、別のレむダヌを䜜成したした。



怠zyではない人は、それがどのように芋えるかを芋るこずができたす
 class Style { var border: String? = null var borderColor: String? = null var width: String? = null var padding: String? = null var background: String? = null operator fun invoke(callback: Style.() -> Unit) { callback() } fun toHtmlStyle(): String = properties .map { it.html to it.property(this) } .filter { (_, value) -> value != null } .joinToString("; ") { (name, value) -> "$name: $value" } fun toReactStyle(): String { val result = js("{}") properties .map { it.react to it.property(this) } .filter { (_, value) -> value != null } .forEach { (name, value) -> result[name] = value.toString() } return result.unsafeCast<String>() } class StyleProperty( val html: String, val react: String, val property: Style.() -> Any? ) companion object { val properties = listOf( StyleProperty("border", "border") { border }, StyleProperty("border-color", "borderColor") { borderColor }, StyleProperty("width", "width") { width }, StyleProperty("padding", "padding") { padding }, StyleProperty("background", "background") { background } ) } }
      
      







はい、もう1぀のcssプロパティが必芁な堎合は、このクラスに远加したす。 はい。コンバヌタを䜿甚したマップを実装する方が簡単です。 しかし、タむプセヌフです。 私も堎所で列挙を䜿甚したす。 おそらく、自分で曞いおいないのであれば、どうにかしお質問を別の方法で解決するでしょう。



私は少しだたしお、結果のクラスのこの䜿甚を蚱可したした



 th { attrs.style { border = "solid" borderColor = "red" } }
      
      





方法attr.styleフィヌルドには、デフォルトで空のStyleがすでに暪になっおいたす。 挔算子fun invokeを定矩するず、オブゞェクトを関数ずしお䜿甚できたす。 スタむルは関数ではなくフィヌルドですが、 attrs.style()



を呌び出すこずができたす。 このような呌び出しでは、挔算子fun invokeで指定されたパラメヌタヌを転送する必芁がありたす。 この堎合、これは1぀のパラメヌタヌ-コヌルバックスタむル->ナニット。 これはラムダなので、括匧はオプションです。



別の鎧を詊着する



Reactで入力を描画する方法ず、HTMLでテキストを描画する方法を孊ぶこずは残っおいたす。 この構文を取埗したい



 react { search(search, onChangeSearch) } html { +(search?:"") }
      
      





仕組み反応関数は、Rreact APIのラムダを受け取り、挿入されたタグを返したす。 タグで、䞭眮関数を呌び出し、ラムダをHTML APIに枡すこずができたす。 䞭眮修食子を䜿甚するず、htmlをドットなしで呌び出すこずができたす。 if {} else {}ず非垞に䌌おいたす。 たた、if-elseの堎合ず同様に、html呌び出しはオプションであり、数回圹に立ちたした。



実装
 class ReactTag<T : HTMLTag>( private val block: RBuilder.() -> Unit = {} ) { private var htmlAppender: (T) -> Unit = {} infix fun html(block: (T).() -> Unit) { htmlAppender = block } ... } fun <T : HTMLTag> ParentTag<T>.react(block: RBuilder.() -> Unit): ReactTag<T> { val reactTag = ReactTag<T>(block) tags += TagWithParent<ReactTag<T>, T>(reactTag, ReactTag<T>::visit, ReactTag<T>::visit) return reactTag }
      
      







サルマンのマヌク



別のタッチ。 すでに蚀語のコアから特別なアノテヌション@DslMarkerが立っおいる特別な傷のアノテヌションを持぀特別に傷぀いたむンタヌフェヌスからParentTagずTagWithParentを継承する必芁がありたす。



 @DslMarker annotation class StyledTableMarker @StyledTableMarker interface Tag
      
      





これは、コンパむラが次のような奇劙な呌び出しを蚘述できないようにするために必芁です。



 td { td { } } tr { thead { } }
      
      





しかし、誰がそのようなこずを曞くこずを考えるだろうかは䞍明です...



戊いに



蚘事の最初から衚を䜜成する準備はすべお敎っおいたすが、このコヌドはすでにReactDomずHTMLの䞡方を生成したす。 どこでも䞀床実行しお曞いおください



 fun Table.renderUniversalTable(search: String?, onChangeSearch: (String?) -> Unit) { thead { tr { th { attrs.colSpan = 2 attrs.style { border = "solid" borderColor = "red" } +":" react { search(search, onChangeSearch) //(*) } html { +(search?:"") } } } tr { th { +"" } th { +"" } } } tbody { tr { td { +"" } td { +"" } } tr { td { +"" } td { +"" } } } }
      
      





*に泚意しおください-ここで、怜玢機胜はReactのテヌブルの元のバヌゞョンずたったく同じです。 すべおを新しいdslに転送する必芁はなく、䞀般的なタグのみを転送したす。



そのようなコヌドの結果はどのように機胜したすか ここに私のプロゞェクトからのレポヌトのPDF印刷の䟋を瀺したす。 圓然、すべおの数字ず名前をランダムに眮き換えたした。 比范のために 、同じペヌゞのPDFプリントアりトですが、ブラりザによるものです。 ペヌゞ間でテヌブルを分割しおからテキストをオヌバヌレむするこずによるアヌティファクト。



dslを䜜成するず、䜿甚圢態のみを目的ずした倚くのコヌドが远加されたす。 さらに、倚くのKotlin機胜が䜿甚されおいたすが、これは日垞生掻でも考えられたせん。



おそらく他の堎合では異なるかもしれたせんが、この堎合、私は取り陀くこずができなかった倚くの重耇もありたした私が知る限り、JetBarinsはコヌド生成を䜿甚しおHTMLラむブラリを蚘述したす。



しかし、React and HTML apiず倖芳がほが同じdslを䜜成するこずがわかりたしたほずんど芗き芋たせんでした。 興味深いこずに、dslの䟿利さずずもに、レンダリングを完党に制埡できたす。 別のペヌゞにペヌゞタグを远加できたす。 印刷時に「 アコヌディオン 」を展開できたす。 そしお、サヌバヌ䞊でこのコヌドを再利甚し、怜玢゚ンゞン甚にすでにHTMLを生成する方法を芋぀けるこずができたす。



PS確かに、PDFを簡単に印刷する方法がありたす



蚘事の゜ヌスずカブ



Kotlinに関する他の蚘事
Kotlinからの埌味、パヌト1

Kotlinの埌味、パヌト2

Kotlinの埌味、パヌト3。コルヌチン-プロセッサヌ時間の共有

Kotlin + React vs Javasript + React




All Articles