这阵子在研究Kotlin,它提供了类似DSL的语法能力,一些在Java中写起来冗长的方法,在Kotlin中则可以方便的使用,同时具有很高的可读性。
举个例子,如果我们要构造这样的xml:
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="UTF-8"?> <student enable="true"> <name>张三</name> <gender>男</gender> <remark/> </student> 复制代码
|
如果使用Java来做的话,是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException;
public class XmlExample { public static void main(String[] args) throws ParserConfigurationException { final var document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); document.setXmlStandalone(true); final var student = document.createElement("student"); student.setAttribute("enable", "true"); document.appendChild(student); final var name = document.createElement("name"); name.appendChild(document.createTextNode("张三")); student.appendChild(name); final var gender = document.createElement("gender"); gender.appendChild(document.createTextNode("男")); student.appendChild(gender); student.appendChild(document.createElement("remark")); } } 复制代码
|
简单的例子看起来还算清晰,但如果层级变多了可读性会迅速下降。
接下来给大伙整个活,我用Kotlin写一个DSL,效果是这样的:
1 2 3 4 5 6 7 8 9 10
| fun main() { document { "student"("enable" to "true") { "name"{ +"张三" } "gender"{ +"男" } "remark"() } } } 复制代码
|
可以看出代码和xml的结构是一一对应的,这样我们就非常方便地构造了一个xml实例。
以上效果的全部实现代码包括import在内仅53行,并且支持格式化输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import org.w3c.dom.Document import org.w3c.dom.Node import java.io.ByteArrayOutputStream import javax.xml.parsers.DocumentBuilderFactory import javax.xml.transform.OutputKeys import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource import javax.xml.transform.stream.StreamResult
private val defaultDocumentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() private val defaultTransformerFactory = TransformerFactory.newInstance()
fun document(block: DocumentBuilderDsl.() -> Unit): Document = defaultDocumentBuilder.newDocument().apply { xmlStandalone = true block(DocumentBuilderDsl(this)) }
@DslMarker @Target(AnnotationTarget.CLASS) annotation class XmlDsl
@XmlDsl class DocumentBuilderDsl(private val document: Document) { operator fun String.invoke(vararg attributes: Pair<String, String?>): Node = this(*attributes) {} operator fun String.invoke(vararg attributes: Pair<String, String?>, block: NodeBuilderDsl.() -> Unit): Node = document.appendChild(document.createElement(this).apply { attributes.forEach { setAttribute(it.first, it.second) } block(NodeBuilderDsl(document, this)) }) }
@XmlDsl class NodeBuilderDsl(private val document: Document, private val node: Node) { operator fun String.invoke(vararg attributes: Pair<String, String?>): Node = this(*attributes) {} operator fun String.invoke(vararg attributes: Pair<String, String?>, block: NodeBuilderDsl.() -> Unit): Node = node.appendChild(document.createElement(this).apply { attributes.forEach { setAttribute(it.first, it.second) } block(NodeBuilderDsl(document, this)) })
operator fun String.unaryPlus(): Node = node.appendChild(document.createTextNode(this)) }
fun Document.asXml(format: Boolean = false, indentAmount: Int = 4): String = ByteArrayOutputStream().use { defaultTransformerFactory.newTransformer().apply { if (format) { setOutputProperty(OutputKeys.INDENT, "yes") setOutputProperty("{http://xml.apache.org/xslt}indent-amount", indentAmount.toString()) setOutputProperty(OutputKeys.STANDALONE, "yes") } }.transform(DOMSource(this), StreamResult(it)) it.toString() } 复制代码
|
还有用poi构造Excel的也可以这样玩,如果我们要构造一个表格:
那么我们就可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fun main() { val workbook = workbook<XSSFWorkbook> { sheet { row { cell { setCellValue("姓名") } cell { setCellValue("性别") } } row { cell { setCellValue("张三") } cell { setCellValue("男") } } } } workbook.write(File("src/main/resources/test.xlsx")) } 复制代码
|
实现代码比上面的xml还少,还支持合并单元格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import org.apache.poi.hssf.usermodel.HSSFWorkbook import org.apache.poi.ss.usermodel.* import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.xssf.usermodel.XSSFWorkbook import java.io.File import java.io.FileOutputStream
inline fun <reified T : Workbook> workbook(block: WorkbookBuilderDsl.() -> Unit): Workbook { val workbook = when (T::class) { HSSFWorkbook::class -> HSSFWorkbook() XSSFWorkbook::class -> XSSFWorkbook() else -> error("不支持的类型:${T::class}") } block(WorkbookBuilderDsl(workbook)) return workbook }
@DslMarker @Target(AnnotationTarget.CLASS) annotation class WorkbookDsl
@WorkbookDsl class WorkbookBuilderDsl(private val workbook: Workbook) { fun sheet(sheetName: String? = null, block: SheetBuilderDsl.() -> Unit): Sheet = (if (sheetName != null) workbook.createSheet(sheetName) else workbook.createSheet()).also { block(SheetBuilderDsl(it)) } }
@WorkbookDsl class SheetBuilderDsl(private val sheet: Sheet) { private var rownum = 0 fun row(block: RowBuilderDsl.() -> Unit): Row = sheet.createRow(rownum).also { block(RowBuilderDsl(it, rownum++)) } }
@WorkbookDsl class RowBuilderDsl(private val row: Row, private val rownum: Int) { private var column = 0 private val sheet = row.sheet fun cell(type: CellType? = null, rowspan: Int = 1, colspan: Int = 1, block: Cell.() -> Unit): Cell { if (colspan > 1 || rowspan > 1) sheet.addMergedRegion(CellRangeAddress(rownum, rownum + rowspan - 1, column, column + colspan - 1)) sheet.mergedRegions .firstOrNull { rownum in it.firstRow..it.lastRow && column in it.firstColumn..it.lastColumn } ?.also { if (rownum != it.firstRow || column != it.firstColumn) column = it.lastColumn + 1 } return (if (type != null) row.createCell(column++, type) else row.createCell(column++)).also(block) } }
fun Workbook.write(file: File) { use { it.write(FileOutputStream(file)) } } 复制代码
|
有兴趣的同学可以玩下,当然这些只是实现了核心功能,如果要完善的实现可以根据情况自行修改,有时间的话我也打算就以上的内容放到Github分享出来。
作者:Gigaplant
链接:https://juejin.cn/post/7011486906995179528
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。