跳到主要内容

标准数据域

提示
  • Nop 平台还处于开发阶段, 本文档中的实践方案可能会部分失效,但本人精力有限,无法及时跟进,请自行按照最新代码调整;
  • 您可以与 DeepWiki 进行问答互动(支持中文)以深入学习 Nop 平台的设计与实现;
  • 若此文对您有很大帮助,请投币支持一下吧;
版权声明

前言

数据域主要用于在 XDef 中声明节点属性的类型,在生成的 class 模型中, 成员变量也将被定义为数据域所对应的「目标数据类型」,而在解析 DSL 时, 也会自动根据数据域的约束对相应的属性值进行解析和校验,从而确保 DSL 模型的完整性和有效性:

  • 定义 XDef

    example.xdef
    <!-- 定义 Example 的属性类型 -->
    <example xdef:name="Example"
    name="string" type="generic-type" date="datetime"
    />
  • 生成 class

    _Example.java
    // 根据数据域所代表的目标数据类型生成 Example 的成员变量
    public class _Example ... {
    private java.lang.String _name;
    private io.nop.core.type.IGenericType _type;
    private java.time.LocalDateTime _date;
    }
  • 编写 DSL

    <example name="Example" type="string" date="2025-06-06 08:55:21" />

    <!-- Note: 通过表达式 ${xxx} 可以直接向属性直接传递非字符串数据 -->
    <c:script><![CDATA[
    import java.util.Date;

    const date = new Date();
    ]]></c:script>
    <example name="Example" type="string" date="${date}" />

    其等价于构造对象

    Example example = new Example();
    example.name = "Example";
    example.type = new PredefinedRawType("java.lang.String", String.class, "STRING_TYPE");
    example.date = ConvertHelper.toLocalDateTime("2025-06-06 08:55:21");

标准数据域的枚举值定义在 /dict/core/std-domain.dict.yaml 中,但其并不完整,完整的可选值详见 XDefConstants 中以 STD_DOMAIN_ 开头的常量值。

对标准数据域字典项的解析和校验在 SimpleStdDomainHandlersXplStdDomainHandlersConverterStdDomainHandler 等中实现,并通过 StdDomainRegistry#registerStdDomainHandler 进行注册。

nop-xlang 模块中已注册的数据域详见 StdDomainRegistry#registerDefaults, 本手册所收录的数据域便来自于此。若是需要自定义数据域, 可参考《XDef - 注册并使用属性类型》注册新的类型。

基础数据域

def-type

名称目标数据类型
def-type

io.nop.xlang.xdef.XDefTypeDecl

一般在元元模型中设定,用于支持为元模型中的相应属性指定不同的数据域, 也就是,其描述的是 DSL 属性的属性类型。

其内容格式为 (!~#)?{stdDomain}(:{options})?(={defaultValue})?,如 !dict:core/some-dict=dict1

针对该格式的详细说明见 XDef 属性类型

其中,stdDomain 可以为包含 def-type 自身在内的所有注册到 StdDomainRegistry 中的数据域的名字:

/nop/schema/xdef.xdef
  <!-- ... -->
<meta:define ...
xdef:unknown-attr="def-type"
>
<!-- ... -->
</meta:define>
<!-- ... -->
<example
xmlns:x="/nop/schema/xdsl.xdef"
xmlns:xdef="/nop/schema/xdef.xdef"
x:schema="/nop/schema/xdef.xdef"
>
<child-1 name="!var-name" />
<child-2 refs="v-path-list" />
</example>

:其由 SimpleStdDomainHandlers.DefTypeType 对源数据做类型转换。

xdef-attr

名称目标数据类型
xdef-attr

io.nop.xlang.xdef.impl.XDefAttribute

用于定义 XDef 的属性,解析生成的是 XDef 属性结构对象 XDefAttribute

当前仅用于元元模型 /nop/schema/xdef.xdef 中,以对其自身进行交叉定义

/nop/schema/xdef.xdef
  <!-- ... -->
<meta:define
meta:unknown-attr="!xdef-attr"
xdef:name="var-name"
xdef:value="def-type"
xdef:default-override="enum:io.nop.xlang.xdef.XDefOverride"
xdef:unknown-attr="def-type"
...
>
<!-- ... -->
</meta:define>
<!-- ... -->

其中,meta:unknown-attr="!xdef-attr" 将用于描述所有的 xdef 名字空间的属性,且这些属性的值均为 def-type 类型。

所谓交叉定义就是,meta:unknown-attr="!xdef-attr" 定义了 xdef:unknown-attr="def-type",而 xdef:unknown-attr="def-type" 又反过来定义了 meta:unknown-attr="!xdef-attr"

:其由 SimpleStdDomainHandlers.XdefAttrType 对源数据做类型转换。

any

名称目标数据类型
any

java.lang.Object

接受任意类型的源数据,并保持不变,不对其做类型转换。

:其由 ConverterStdDomainHandler 调用 StdDataType#ANY 对应的转换器对源数据做类型转换。

string

名称目标数据类型
string

java.lang.String

通过 ConvertHelper#toString 按如下规则将源数据转换为 String 类型:

  • null 值不做转换,直接返回 null
  • MonthDay 类型数据通过 ConvertHelper#monthDayToString 转换为 MM-dd 形式的字符串,如 12-08
  • LocalDateTime 类型数据通过 ConvertHelper#localDateTimeToString 转换为 yyyy-MM-dd HH:mm:ss 形式的字符串,如 2025-06-05
  • 其他类型数据直接调用其 #toString() 函数得到对应的字符串;

:其由 ConverterStdDomainHandler 调用 StdDataType#STRING 对应的转换器对源数据做类型转换。

int

名称目标数据类型
int

java.lang.Integer

通过 ConvertHelper#toInt 按如下规则将源数据转换为 Integer 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #intValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToInt 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToInt 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串调用 Integer#parseInt(String) 将其转换为 Integer 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#INT 对应的转换器对源数据做类型转换。

short

名称目标数据类型
short

java.lang.Short

通过 ConvertHelper#toShort 按如下规则将源数据转换为 Short 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #shortValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToShort 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToShort 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串调用 Short#parseShort(String) 将其转换为 Short 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#SHORT 对应的转换器对源数据做类型转换。

long

名称目标数据类型
long

java.lang.Long

通过 ConvertHelper#toLong 按如下规则将源数据转换为 Long 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #longValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToLong 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToLong 按以下规则进行转换:
    • 空字符串对应为 null
    • 若为 数字+后缀 形式,则按不同后缀得到相应的字节数,如 123G 对应的是 123 * 1024 * 1024 * 1024L 的计算结果。可选后缀为:
      • G/gGB)、M/mMB)、K/kKB
    • 其他字符串调用 Long#parseLong(String) 将其转换为 Long 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#LONG 对应的转换器对源数据做类型转换。

float

名称目标数据类型
float

java.lang.Float

通过 ConvertHelper#toFloat 按如下规则将源数据转换为 Float 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #floatValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToFloat 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToFloat 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串调用 Float#parseFloat(String) 将其转换为 Float 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#FLOAT 对应的转换器对源数据做类型转换。

double

名称目标数据类型
double

java.lang.Double

通过 ConvertHelper#toDouble 按如下规则将源数据转换为 Double 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #doubleValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToDouble 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToDouble 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串调用 Double#parseDouble(String) 将其转换为 Double 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#DOUBLE 对应的转换器对源数据做类型转换。

char

名称目标数据类型
char

java.lang.Character

通过 ConvertHelper#toChar 按如下规则将源数据转换为 Character 类型:

  • null 值不做转换,直接返回 null
  • Character 类型数据不做转换,直接返回原值;
  • Number 类型数据调用其 #intValue() 并强制转换为 char 后得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToChar 按以下规则进行转换:
    • true 对应为 '1'
    • false 对应为 '0'
  • String 类型数据通过 ConvertHelper#stringToChar 按以下规则进行转换:
    • 空字符串对应为 null
    • 若字符串长度为 1,则取其唯一字符(.charAt(0))作为最终结果;
    • 其他字符抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#CHAR 对应的转换器对源数据做类型转换。

byte

名称目标数据类型
byte

java.lang.Byte

通过 ConvertHelper#toByte 按如下规则将源数据转换为 Byte 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #byteValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToByte 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToByte 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串调用 ConvertHelper#stringToShort 将其转换为 Short,再由 Short#byteValue() 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#BYTE 对应的转换器对源数据做类型转换。

boolean

名称目标数据类型
boolean

java.lang.Boolean

通过 ConvertHelper#toBoolean 按如下规则将源数据转换为 Boolean 类型:

  • null 值不做转换,直接返回 null
  • Boolean 类型数据不做转换,直接返回原值;
  • IntegerLongNumber 类型数据不为 0 时,返回 true,否则,返回 false
  • String 类型数据通过 ConvertHelper#stringToBoolean 按以下规则进行转换:
    • 空字符串对应为 null
    • 1trueYy 对应为 true
    • 0falseNn 对应为 false
    • 其他字符抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#BOOLEAN 对应的转换器对源数据做类型转换。

boolFlag

名称目标数据类型
boolFlag

java.lang.Byte

通过 ConvertHelper#toByte 按如下规则将源数据转换为 Byte 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据调用其 #byteValue() 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToByte 按以下规则进行转换:
    • true 对应为 1
    • false 对应为 0
  • String 类型数据通过 ConvertHelper#stringToByte 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串调用 ConvertHelper#stringToShort 将其转换为 Short,再由 Short#byteValue() 得到结果,若转换失败,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 SimpleStdDomainHandlers.BoolFlagType 对源数据做类型转换。

decimal

名称目标数据类型
decimal

java.math.BigDecimal

通过 ConvertHelper#toBigDecimal 按如下规则将源数据转换为 BigDecimal 类型:

  • null 值不做转换,直接返回 null
  • BigDecimal 类型数据不做转换,直接返回原值;
  • BigIntegerIntegerLongShort 类型数据调用 BigDecimal#valueOf(long) 得到结果;
  • Character 类型数据调用 BigDecimal#valueOf(int) 得到结果;
  • Boolean 类型数据通过 ConvertHelper#booleanToBigDecimal 按以下规则进行转换:
    • true 对应为 1new BigDecimal((int) 1));
    • false 对应为 0new BigDecimal((int) 0));
  • StringNumber 类型数据,将其字符串(#toString())结果由 ConvertHelper#stringToBigDecimal 按以下规则进行转换:
    • 直接调用构造函数 BigDecimal(String) 得到结果,若构造发生异常,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#DECIMAL 对应的转换器对源数据做类型转换。

bigint

名称目标数据类型
bigint

java.math.BigInteger

通过 ConvertHelper#toBigInteger 按如下规则将源数据转换为 BigInteger 类型:

  • null 值不做转换,直接返回 null
  • BigInteger 类型数据不做转换,直接返回原值;
  • BigDecimal 类型数据调用其 #toBigInteger() 得到结果;
  • Number 类型数据调用其 #longValue() 得到其 long 值再由 BigInteger#valueOf(long) 得到结果;
  • Character 类型数据调用 BigInteger#valueOf(int) 得到结果;
  • Boolean 类型数据按以下规则进行转换:
    • true 对应为 1BigInteger.valueOf((long) 1));
    • false 对应为 0BigInteger.valueOf((long) 0));
  • String 类型数据直接调用构造函数 BigInteger(String) 得到结果, 若构造发生异常,则抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#BIGINT 对应的转换器对源数据做类型转换。

number

名称目标数据类型
number

java.lang.Number

通过 SimpleStdDomainHandlers.NumberType#parseProp 按如下规则将源数据转换为 Number 类型:

  • null 值不做转换,直接返回 null
  • Number 类型数据不做转换,直接返回原值;
  • Timestamp 类型数据通过 ConvertHelper#timestampToLong 取其毫秒值作为最终结果;
  • LocalDate 类型数据通过 ConvertHelper#localDateToMillis 取其毫秒值作为最终结果;
  • LocalDateTime 类型数据通过 ConvertHelper#localDateTimeToMillis 取其毫秒值作为最终结果;
  • String 类型数据通过 ConvertHelper#stringToNumber 按以下规则进行转换:
    • 空字符串对应为 null
    • 若以 -- 开头,则直接抛出转换异常;
    • 若以 0x/-0x 开头,则通过 Integer#decode 按十六进制解析为 Integer。 若其还包含后缀 L/l,则在去掉后缀后通过 Long#decode 按十六进制解析为 Long
    • 若包含后缀 L/lF/fD/d,则分别按照 LongFloatDouble 进行解析;
    • 若不包含小数点 .,则依次尝试按 IntegerLongBigInteger 进行解析, 也就是,在数字位数超过类型限制时,继续尝试下一种类型,直到解析成功或全部失败;
    • 若包含小数点 .,则依次尝试按 DoubleBigDecimal 进行解析, 也就是,在数字位数超过类型限制时,继续尝试下一种类型,直到解析成功或全部失败;
  • 其他类型数据抛出转换异常;

date

名称目标数据类型
date

java.time.LocalDate

通过 ConvertHelper#toLocalDate 按如下规则将源数据转换为 LocalDate 类型:

  • null 值不做转换,直接返回 null
  • LocalDate 类型数据不做转换,直接返回原值;
  • LocalDateTime 类型数据调用其 #toLocalDate() 得到结果;
  • Timestamp 类型数据通过 ConvertHelper#timestampToLocalDate 按以下规则进行转换:
    • 调用 Timestamp#toLocalDateTime().toLocalDate() 得到结果;
  • DateLong 类型数据通过 ConvertHelper#millisToLocalDate 按以下规则进行转换:
    • 通过 Calendar 将毫秒值转换为 LocalDate
  • String 类型数据通过 ConvertHelper#stringToLocalDate 按以下规则进行转换:
    • 空字符串对应为 null
    • 若为纯数字,则将其转为 Long 后再通过 ConvertHelper#millisToLocalDate 进行转换;
    • 按形式 yyyy-MM-ddLocalDate 解析和转换,若字符串为 yyyy-MM-dd 00:00:00 形式,则会在去掉 00:00:00 后再做转换;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#DATE 对应的转换器对源数据做类型转换。

time

名称目标数据类型
time

java.time.LocalTime

通过 ConvertHelper#toLocalTime 按如下规则将源数据转换为 LocalTime 类型:

  • null 值不做转换,直接返回 null
  • LocalTime 类型数据不做转换,直接返回原值;
  • String 类型数据通过 ConvertHelper#stringToLocalTime 按以下规则进行转换:
    • 空字符串对应为 null
    • 其他字符串按形式 HH:mm:ssLocalTime 解析和转换;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#TIME 对应的转换器对源数据做类型转换。

datetime

名称目标数据类型
datetime

java.time.LocalDateTime

通过 ConvertHelper#toLocalDateTime 按如下规则将源数据转换为 LocalDateTime 类型:

  • null 值不做转换,直接返回 null
  • LocalDateTime 类型数据不做转换,直接返回原值;
  • LocalDate 类型数据由 LocalDateTime#of 对其时分秒补零后得到结果;
  • Timestamp 类型数据通过 ConvertHelper#timestampToLocalDateTime 按以下规则进行转换:
    • 调用 Timestamp#toLocalDateTime 得到结果;
  • Date 类型数据通过 ConvertHelper#millisToLocalDateTime 按以下规则进行转换:
    • 将毫秒值转换为 Timestamp 后,再调用 Timestamp#toLocalDateTime 得到结果;
  • String 类型数据通过 ConvertHelper#stringToLocalDateTime 按以下规则进行转换:
    • 空字符串对应为 null
    • 若为纯数字,则将其转为 Long 后再通过 ConvertHelper#millisToLocalDateTime 进行转换;
    • 若为 yyyy-MM-dd 形式,则通过 ConvertHelper#stringToLocalDate 将其转换为 LocalDate,再调用 LocalDate#atStartOfDay() 得到结果;
    • 其他字符串按形式 yyyy-MM-dd HH:mm:ssyyyy-MM-ddTHH:mm:ss.SSSZyyyy-MM-ddTHH:mm:ssLocalDateTime 解析和转换;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#DATETIME 对应的转换器对源数据做类型转换。

timestamp

名称目标数据类型
timestamp

java.sql.Timestamp

通过 ConvertHelper#toTimestamp 按如下规则将源数据转换为 Timestamp 类型:

  • null 值不做转换,直接返回 null
  • Timestamp 类型数据不做转换,直接返回原值;
  • LongDate 类型数据作为毫秒值构造 Timestamp
  • LocalDateTime 类型数据通过 ConvertHelper#localDateTimeToTimestamp 按以下规则进行转换:
    • 调用 Timestamp#valueOf(LocalDateTime) 得到结果;
  • LocalDate 类型数据通过 ConvertHelper#localDateToTimestamp 按以下规则进行转换:
    • LocalDateTime#of 对其时分秒补零后,再调用 ConvertHelper#localDateTimeToTimestamp 得到结果;
  • String 类型数据通过 ConvertHelper#stringToTimestamp 按以下规则进行转换:
    • 空字符串对应为 null
    • 若为纯数字,则将其转为 Long 后,作为毫秒值构造 Timestamp
    • 若为 yyyy-MM-dd 形式,则通过 ConvertHelper#stringToLocalDate 将其转换为 LocalDate,再调用 LocalDate#atStartOfDay() 得到 LocalDateTime,最后,由 ConvertHelper#localDateTimeToTimestamp 转换得到结果;
    • 其他字符串按形式 yyyy-MM-dd HH:mm:ssyyyy-MM-ddTHH:mm:ss.SSSZyyyy-MM-ddTHH:mm:ssLocalDateTime 解析和转换,最后,由 ConvertHelper#localDateTimeToTimestamp 转换得到结果;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#TIMESTAMP 对应的转换器对源数据做类型转换。

duration

名称目标数据类型
duration

java.time.Duration

通过 ConvertHelper#toDuration 按如下规则将源数据转换为 Duration 类型:

  • null 值不做转换,直接返回 null
  • Duration 类型数据不做转换,直接返回原值;
  • String 类型数据通过 ConvertHelper#stringToDuration 按以下规则进行转换:
    • 空白字符串对应为 null
    • 若为纯数字,则将其转为 Long 后,作为毫秒值构造 Duration
    • 若为 数字+后缀 形式,如 123s,则按不同后缀构造相应的毫秒或纳秒(仅针对 nsus 后缀)Duration。可选后缀为:
      • ns(纳秒)、us(微秒)、ms(毫秒)、s(秒)、m(分)、h(时)、d(天)
    • 若为 PnDTnHnMn.nS 形式(n 可替换为任意数字),如 P2DT3H4M20.345S(表示 2 days, 3 hours and 4 minutes 20.345 seconds), 其由 Duration#parse 解析得到结果;
    • 其他字符抛出转换异常;
  • 其他类型数据抛出转换异常;

:其由 ConverterStdDomainHandler 调用 StdDataType#DURATION 对应的转换器对源数据做类型转换。

enum

名称目标数据类型
enum

由其 XDef 属性类型{options} 部分的值确定:

  • 若其包含 |,则数据类型为 java.lang.String
  • 若其为有效的 class-name(必须为枚举类), 则数据类型为该 class,如 io.nop.xlang.xdef.XDefOverride

枚举类型。其有效值必须满足:

  • 若定义的属性类型形式为 enum:a|b|c|d,即,{options} 部分为以 | 为分隔符的字符串常量,则有效值只能从该列表(abcd)中取值;
  • {options} 部分对应的是枚举类名,如 enum:io.nop.xlang.xdef.XDefOverride, 则有效值只能为:
    • 若枚举项以 io.nop.api.core.annotations.core.Option 标注,则有效值必须为设定的 Option#value 的值;
    • 否则,有效值为枚举项的名字;

需要注意的是,对于以 Option#value 作为有效值的枚举类,必须提供 @StaticFactoryMethod 标注的 public static 方法,且其有且只有一个 String 类型的参数:

io.nop.wf.core.model.WfExecGroupType
public enum WfExecGroupType {
@Option("none")
NONE("none"),
@Option("or-group")
OR_GROUP("or-group"),
...;

@StaticFactoryMethod
public static WfExecGroupType fromText(String text) {
return textMap.get(text);
}

private String text;
WfExecGroupType(String text) {
this.text = text;
}

private static final Map<String, WfExecGroupType> textMap = new HashMap<>();
static {
for (WfExecGroupType value : values()) {
textMap.put(value.text, value);
}
}
}

在从 DSL 到 Java 对象的解析过程中,将会调用枚举类中的该方法将 DSL 中的文本属性值转换为对应的枚举项。

具体转换逻辑为 DslModelParser#doParseNode0 -> DslBeanModelParser#parseObject -> IBeanModel#setProperty -> BeanPropertyModel#setPropertyValue -> MethodPropertySetter#setProperty -> IFunctionModel#call1 -> IFunctionArgument#castArg -> FunctionArgument#castArg,再由 FunctionArgument#converter 将参数转换为目标类型。其中,#converterMethodModelBuilder#buildConverter 根据目标数据类型构造而来,对于枚举类,该转换器将为 EnumTypeConverter 类型,而在 EnumTypeConverter#convert 中则会将 String 类型参数通过 @StaticFactoryMethod 标注的静态方法转换为相应的枚举项。

dict

名称目标数据类型
dict

regex

名称目标数据类型
regex

java.lang.String

其仅在从 DSL 到 Java 对象的解析过程中,通过 RegexHelper#compileRegex 对该类型的值进行有效性检查,并不会将其解析为相应的正则表达式对象,其类型始终为 String

可通过 RegexHelper#registerRegexCompiler 注册所需的正则表达式编译器 IRegexCompiler,对 regex 类型的格式也由该编译器决定。Nop 采用 JdkRegexCompiler 作为缺省的正则表达式编译器,也即,该类型的数据格式由 java.util.regex.Pattern 决定。

string-map

名称目标数据类型
string-map

boolean-or-string

名称目标数据类型
boolean-or-string

boolean-or-number

名称目标数据类型
boolean-or-number

int-or-string

名称目标数据类型
int-or-string

int-size

名称目标数据类型
int-size

int-list

名称目标数据类型
int-list

int-range

名称目标数据类型
int-range

long-size

名称目标数据类型
long-size

long-range

名称目标数据类型
long-range

xdef-ref

名称目标数据类型
xdef-ref

java.lang.String

用于引用当前 XDef 中的 xdef 命名片段(由 xdef:name 指定其名字):

<example
xmlns:x="/nop/schema/xdsl.xdef"
xmlns:xdef="/nop/schema/xdef.xdef"
x:schema="/nop/schema/xdef.xdef"
>
<xdef:define xdef:name="ChildBase" .../>

<child xdef:ref="ChildBase" />
</example>

xdef:ref/nop/schema/xdef.xdef 中的定义为 xdef:ref="xdef-ref"

该类型也可以用于引用外部元模型文件:

/nop/schema/query/query.xdef
<query x:schema="/nop/schema/xdef.xdef"
xmlns:x="/nop/schema/xdsl.xdef"
xmlns:xdef="/nop/schema/xdef.xdef"
>
<filter xdef:ref="filter.xdef"/>
<orderBy xdef:ref="order-by.xdef"/>
<groupBy xdef:ref="group-by.xdef"/>
</query>

:其由 SimpleStdDomainHandlers.XDefRefType 对源数据做类型转换。

method-ref

名称目标数据类型
method-ref

io.nop.xlang.expr.MethodRef

对 Java class 方法的引用。其格式为 {ownerName}::{methodName},其中,{ownerName} 为 Java class 全类名,{methodName} 为该类中的方法名,如 io.nop.xlang.expr.MethodRef::parse

:其由 SimpleStdDomainHandlers.MethodRefType 对源数据做类型转换。

generic-type

名称目标数据类型
generic-type

io.nop.core.type.IGenericType

Java 泛型类型。源数据可以直接使用以下内置类型的名称:

  • anyvoid
  • stringnumber
  • charbytebooleanintlongshortfloatdouble
  • ObjectVoid
  • CharacterByteBooleanIntegerLongShortFloatDouble
  • MapListSetCollectionBigDecimal
  • never(对应 io.nop.commons.lang.Never)、unknown(对应 io.nop.commons.lang.Unknown);
  • PageBean(对应 io.nop.api.core.beans.PageBean)、XNode(对应 io.nop.core.lang.xml.XNode)、NopScriptError(对应 io.nop.api.core.exceptions.NopScriptError);

对于其它类型则均需要使用其 class 全名称,如 java.util.function.Consumerio.nop.api.core.beans.graphql.GraphQLConnection 等。

对于泛型参数,可直接使用内置类型名称或 class 全名称,如 Map<String,PageBean>PageBean<io.nop.auth.dao.entity.NopAuthUser>

对于数组类型,只需要在类型名后加 [] 即可,如 Map[]char[]io.nop.commons.type.FileReference[] 等。

若源数据为 (arg1:int,arg2:Map<String,Integer>) => boolean 形式的箭头函数, 则会将其转换为 IFunctionType 类型,其中,arg1/arg2 为参数名,其 : 后面紧跟的是参数类型,若不指定参数类型,则缺省为 any,而 => 后紧跟的则为函数的返回值类型。 函数的参数和返回值类型均按照前面的规则解析得到 IGenericType

:其由 SimpleStdDomainHandlers.GenericTypeType 调用 IGenericTypeParser#parseFromText 对源数据的字符串结果(#toString())做类型转换。

generic-type-list

名称目标数据类型
generic-type-list

java.util.List<io.nop.core.type.IGenericType>

通过 SimpleStdDomainHandlers.GenericTypeListType#parseProp 按如下规则将源数据转换为 List<IGenericType> 类型:

  • List 类型数据不做转换,直接返回原值;
  • 其他源数据调用 #toString() 得到其字符串结果,再通过 GenericTypeParser#parseGenericTypeList 以逗号为分隔符, 如 int,bool,java.util.Map<java.lang.String,java.util.Map>, 从该字符串中解析得到 List<IGenericType>,其中,逗号分隔的每一项都按 generic-type 进行解析,且泛型参数列表中的逗号不会被视为分隔符, 不影响解析的正确性;

std-domain

名称目标数据类型
std-domain

java.lang.String

在 DSL 中,对于该类型的属性,可按需填写包括 std-domain 自身在内的任一注册到 StdDomainRegistry 中的数据域的名字:

/nop/schema/xlib.xdef
<lib ...>
<tags ...>
<xdef:unknown-tag ...>
<attr name="!xml-name"
stdDomain="std-domain"
.../>
</xdef:unknown-tag>
</tags>
</lib>
/nop/web/xlib/web.xlib
<lib xmlns:x="/nop/schema/xdsl.xdef"
x:schema="/nop/schema/xlib.xdef"
>
<tags>
<GenPage outputMode="xjson">
<attr name="view"
stdDomain="v-path"
.../>
<!-- ... -->
</GenPage>
</tags>
</lib>

:其由 SimpleStdDomainHandlers.StdDomainType 对源数据做类型转换。

std-data-type

名称目标数据类型
std-data-type

io.nop.commons.type.StdDataType

:其由 SimpleStdDomainHandlers.StdType 对源数据做类型转换。

std-sql-type

名称目标数据类型
std-sql-type

io.nop.commons.type.StdSqlType

:其由 SimpleStdDomainHandlers.StdSqlTypeType 对源数据做类型转换。

std-sql-type-list

名称目标数据类型
std-sql-type-list

java.util.List<io.nop.commons.type.StdSqlType>

:其由 SimpleStdDomainHandlers.StdSqlTypeListType 对源数据做类型转换。

package-name

名称目标数据类型
package-name

java.lang.String

. 号分隔,不包含除 $ 之外的特殊字符。 按照 Java 规范一般应为全小写字符

java-name

名称目标数据类型
java-name

java.lang.String

一般为中英文字母和数字、_ 组成,与 var-name 不同的是 java-name 允许包含字符 $

class-name

名称目标数据类型
class-name

java.lang.String

package-namejava-name 组成,并以 . 分隔

class-name-set

名称目标数据类型
class-name-set

java.util.Set<String>

bean-name

名称目标数据类型
bean-name

java.lang.String

var-name

名称目标数据类型
var-name

java.lang.String

一般为中英文字母和数字、_ 组成,不包含字符 .$。 只能以 _ 或字母开头

prop-name

名称目标数据类型
prop-name

java.lang.String

一般为中英文字母和数字、_ 组成,不包含字符 .$。 只能以 _ 或字母开头

prop-name-set

名称目标数据类型
prop-name-set

java.util.Set<String>

prop-path

名称目标数据类型
prop-path

java.lang.String

由一个或者多个 prop-name 构成,并通过字符 . 来分隔

conf-name

名称目标数据类型
conf-name

java.lang.String

由英文字母、数字、_-. 构成, 不包含 $ 等特殊字符

xml-name

名称目标数据类型
xml-name

java.lang.String

由英文字母、数字、_-:. 构成,不包含 $ 等特殊字符。 名字只能以 _ 或字母开头,并且 -:. 不能连续出现。

:其校验逻辑见 StringHelper#isValidXmlName

ns-name

名称目标数据类型
ns-name

java.lang.String

由英文字母、数字、_- 构成,不包含 $:. 等特殊字符。 名字只能以 _ 或字母开头,并且 - 不能连续出现。

:其实现逻辑见 SimpleStdDomainHandlers.NsNameType,校验逻辑见 StringHelper#isValidXmlNamespaceName

token-name

名称目标数据类型
token-name

nop-module-name

名称目标数据类型
nop-module-name

nop-module-id

名称目标数据类型
nop-module-id

xpl

名称目标数据类型
xpl

Xpl 片段

其输出模式 outputModenone,不允许副作用输出。 其内容最终编译成 IEvalAction 类型。

:其实现逻辑见 XplStdDomainHandlers.XplNoneType

xpl-node

名称目标数据类型
xpl-node

输出 XNode 的 Xpl 片段

其输出模式 outputModenode,执行的过程中会输出 XNode 节点。 其内容最终编译成 IXNodeGenerator 类型。

:其实现逻辑见 XplStdDomainHandlers#XPL_NODE_TYPE

xpl-text

名称目标数据类型
xpl-text

xpl-sql

名称目标数据类型
xpl-sql

xpl-xml

名称目标数据类型
xpl-xml

xpl-html

名称目标数据类型
xpl-html

xpl-predicate

名称目标数据类型
xpl-predicate

xpl-fn

名称目标数据类型
xpl-fn

Xpl 函数

自定义的 Lambda 函数,以 xpl-fn: (arg1: Arg1, arg2: Arg2) => any 包含函数签名的形式定义数据类型,如:

<!-- 可以忽略参数类型 -->
<validator xdef:value="xpl-fn: (value) => boolean" />

在编写其函数体时,可以通过 ${xxx} 形式引用指定的命名参数,如:

<validator><![CDATA[
return ${value} != 0;
]]></validator>

若是使用 Xpl 来输出 XNode 节点,则需要一个设置了 xpl:outputModenode 的节点包装一下:

<!-- <filter xdef:value="xpl-fn: (filter, query) => io.nop.core.lang.xml.XNode" /> -->
<filter>
<and xpl:outputMode="node">
<eq name="status" value="0" />
<gt name="age" value="20" />
</and>
</filter>

:其实现逻辑见 XplStdDomainHandlers.XplFnType

eval-code

名称目标数据类型
eval-code

io.nop.xlang.api.EvalCode

完整的 Xpl 脚本,如:

import io.nop.commons.util.StringHelper;

const s = StringHelper.trimLeft(varInScope);
// 打印结果输出的调试日志,如 Inspect:s=>abc,loc=[3:36:0:0]<unknown>
s.$("Inspect");

得到该类型的结果后,可按如下方式进行调用:

EvalCode code = /* ... */;
IEvalScope scope = XLang.newEvalScope();
scope.setLocalValue("varInScope", " abc");

Object result = code.invoke(scope); // -> abc

如果是以 xdef:value 指定节点内容的类型为 eval-code,则可以在该节点中使用 Xpl 标签函数:

example.xdef
<example>
<source xdef:value="eval-code"/>
</example>
example.xml
<example x:schema="example.xdef">
<source>
<!-- Note: 必须显式指定 xpl 结果输出模式 xpl:outputMode -->
<c:unit xpl:outputMode="node">
<c:if test="${ a == 1}">
<node-1 />
</c:if>
</c:unit>
</source>
</example>

xpl:outputModetextxml 等文本输出模式,则需调用 EvalCode#generateText 才能得到结果。

或者,也可以嵌入 Xpl 脚本:

example.xml
<example x:schema="example.xdef">
<source><![CDATA[
import io.nop.commons.util.StringHelper;

const s = StringHelper.trimLeft(varInScope);
]]></source>
</example>

:支持在 <x:config/> 中统一导入 Java class 或 xlib 标签库。

:其实现逻辑见 XplStdDomainHandlers.EvalCodeType

expr

名称目标数据类型
expr

io.nop.core.lang.eval.IEvalAction(实例类型为 io.nop.xlang.api.ExprEvalAction

可得到计算结果的 Xpl 表达式,如 a > 1b == 1,可以为 a > 1 && b < 2 这样的逻辑组合,也可以是对变量的取值 user.name,但不能是 a = b 这样的赋值语句。

得到该类型的结果后,可按如下方式进行调用:

IEvalAction eval = /* a > 1 */;
IEvalScope scope = XLang.newEvalScope();
scope.setLocalValue("a", 2);

Object result = eval.invoke(scope); // -> true

:可以使用 and 代替 &&,或使用 or 代替 ||

:其实现逻辑见 XplStdDomainHandlers.ExprType

s-expr

名称目标数据类型
s-expr

io.nop.core.lang.eval.IEvalAction(实例类型为 io.nop.xlang.api.ExprEvalAction

通过 ${} 包裹的单个 expr,如 ${ a > 1}${ a > 1 && b < 2 }

得到该类型的结果后,可按如下方式进行调用:

IEvalAction eval = /* ${ a > 1 } */;
IEvalScope scope = XLang.newEvalScope();
scope.setLocalValue("a", 2);

Object result = eval.invoke(scope); // -> true

:其实现逻辑见 XplStdDomainHandlers.SingleExprType

t-expr

名称目标数据类型
t-expr

io.nop.core.lang.eval.IEvalAction(实例类型为 io.nop.xlang.api.ExprEvalAction

在文本中嵌入零个或多个 s-expr 的 Xpl 模版,如 a = ${ a }, b > 1 is ${ b > 1 }

得到该类型的结果后,可按如下方式进行调用:

IEvalAction eval = /* a > 1 is ${ a > 1 } */;
IEvalScope scope = XLang.newEvalScope();
scope.setLocalValue("a", 2);

Object result = eval.invoke(scope); // -> a > 1 is true

:其实现逻辑见 XplStdDomainHandlers.TplExprType

xt-expr

名称目标数据类型
xt-expr

io.nop.core.lang.eval.IEvalAction(实例类型为 io.nop.xlang.api.ExprEvalAction

通过 %{} 包裹的单个 expr,如 %{ a > 1}%{ a > 1 && b < 2 }

得到该类型的结果后,可按如下方式进行调用:

IEvalAction eval = /* %{ a > 1 } */;
IEvalScope scope = XLang.newEvalScope();
scope.setLocalValue("a", 2);

Object result = eval.invoke(scope); // -> true

%{} 用于隔离 XNode transform 阶段的表达式内容。

:其实现逻辑见 XplStdDomainHandlers.XtExprType

xt-value

名称目标数据类型
xt-value

io.nop.core.lang.eval.IEvalAction(实例类型为 io.nop.xlang.api.ExprEvalAction

在文本中嵌入零个或多个 xt-expr 的 Xpl 模版,如 a = %{ a }, b > 1 is %{ b > 1 }

得到该类型的结果后,可按如下方式进行调用:

IEvalAction eval = /* a > 1 is %{ a > 1 } */;
IEvalScope scope = XLang.newEvalScope();
scope.setLocalValue("a", 2);

Object result = eval.invoke(scope); // -> a > 1 is true

:其实现逻辑见 XplStdDomainHandlers.XtValueType

flags-expr

名称目标数据类型
flags-expr

java.util.function.Predicate<Set<String>>

构造逻辑表达式(与/或/非),如 a && b || c,用于判断某个列表中所包含的标记是否满足该表达式:

Predicate<Set<String>> match = /* a && b || c */;

Set<String> enabledFlags = Set.of("d", "e", "a");
if (match.test(enabledFlags)) {
// do something when flags are enabled
}

也即,若表达式中的某个标记存在于 enabledFlags 列表中,则其表示 true,否则其代表 false,最后计算得到整个表达式的结果为 truefalse

在该表达式中,可以使用 EMPTY 表示匹配空标记列表,如 a || EMPTY,或者使用 ANY 表示匹配非空列表中的任意标记。

:可以使用 and 代替 &&or 代替 ||not 代替 !

:其实现逻辑见 SimpleStdDomainHandlers.FlagsExprType

t-report-expr

名称目标数据类型
t-report-expr

io.nop.core.lang.eval.IEvalAction(实例类型为 io.nop.xlang.api.ExprEvalAction

t-expr 相同,只是其在 t-expr 的基础上增加了对 Excel 公式的支持,如 SUM(A3:A5) + C1,同时提供与 Excel 相关的函数,如 SUMPRODUCT 等。

:Excel 函数由 ReportFunctions 提供实现,被 @Description 标注的函数均可调用。

:其实现逻辑见 TemplateReportExprStdDomainHandler

xpath

名称目标数据类型
xpath

io.nop.core.lang.xml.IXSelector<io.nop.core.lang.xml.XNode>

XPath 选择器。其缺省由 DefaultXPathProvider#compile 通过 XPathHelper#parseXSelector 解析得到。

可通过 XPathProvider#registerInstance 替换缺省实现。

其支持以下 XPath 选择器规则:

  • /html/body/div/select: 以绝对路径查找目标节点;
  • div/select: 以相对路径查找目标节点,其从当前节点往下搜索;
  • //div//p: 查找任意层级中的目标节点,其中,// 表示从当前节点往下的任意层级;
  • ..: 选择父节点;
  • *: 以通配符 * 匹配其所在层级中的任意兄弟节点;
  • p[2]: 选择序号为 2<p/> 子节点。其中,序号从 0 开始;
  • div[expr]: 选择在节点上执行的 expr 表达式结果为 truediv 节点。 在表达式中,@属性名 的形式表示取节点上的属性值, 因此,可以在 [] 中根据属性值做判断,如 [ @name != 'Lily' && @age > 20 ]
  • div/:[expr]: 获得在 div 节点上执行 expr 表达式的执行结果。 在表达式中,@属性名 的形式表示取节点上的属性值, 因此,可以在 :[] 中编写复杂表达式,如 :[ 'Gender is ' + (@gender == '1' ? 'female' : 'male') ]
  • div/@attr: 获得 div 节点中名为 attr 的属性的值。 与 div/:[expr] 不同的是,其为静态取值,而后者为动态取值;
  • div/$value: 获得 div 节点值,其对应的是 xdef:value 所指定的节点内容;
  • div/$innerXml: 获得 div 节点内的 xml 内容(字符串);
  • div/$xml: 获得 div 节点自身的 xml 内容(字符串);
  • div/$tag: 获得 div 节点的标签名;
  • div/$text: 获得 div 节点中的文本内容(包含各级子节点的文本内容);

针对该类型数据的使用可参考以下代码:

String xml = "<root> <child a='1'>3</child> <child b='3'/> </root>";
XNode node = XNodeParser.instance().parseFromText(null, xml);

IXSelector<XNode> xpath = XPathHelper.parseXSelector("/root/child/@a");
assertEquals("1", node.selectOne(xpath));

xpath = XPathHelper.parseXSelector("/root/child/$value");
assertEquals("3", node.selectOne(xpath));

xpath = XPathHelper.parseXSelector("//child");
assertEquals("1", ((XNode) node.selectOne(xpath)).attrText("a"));
assertEquals(2, node.selectMany(xpath).size());

:其由 SimpleStdDomainHandlers.XPathType 对源数据做类型转换。

:可通过 XPathOperatorRegistry#registerOperator 注册自定义的 $xx 形式的值选择器。

jpath

名称目标数据类型
jpath

v-path

名称目标数据类型
v-path

v-path-list

名称目标数据类型
v-path-list

name-or-v-path

名称目标数据类型
name-or-v-path

csv-set

名称目标数据类型
csv-set

逗号分隔的字符串集合(无重复)

, 分隔的字符串集合,最终转换为 LinkedHashSet<String> 类型。 若逗号之间为空白,则该项将被忽略,同时,分隔的每一项都会去掉首尾的空白。

:其处理逻辑见 SimpleStdDomainHandlers.CsvSetType#parseProp

csv-list

名称目标数据类型
csv-list

逗号分隔的字符串集合(可重复)

, 分隔的字符串集合,最终转换为 List<String> 类型。 若逗号之间为空白,则该项将被忽略,同时,分隔的每一项都会去掉首尾的空白。

:其处理逻辑见 SimpleStdDomainHandlers.CsvListType#parseProp

multi-csv-set

名称目标数据类型
multi-csv-set

含与/或关系的字符串集合

,| 分隔的字符串集合,如 a,b|c|e,f,最终转换为 List<LinkedHashSet<String>> 类型,用以表达简单的, 列表)和| 列表)的关系, List<?> 内的元素之间为的关系,LinkedHashSet<?> 内的元素之间为的关系。

:其处理逻辑见 SimpleStdDomainHandlers.MultiCsvSetType#parseProp

word-set

名称目标数据类型
word-set

逗号分隔的字符串集合

csv-set 要求相同, 只是,其不能存在包含空白字符的项,否则,将会抛出校验异常。

:其校验逻辑见 SimpleStdDomainHandlers.WordSetType#isValidItem

tag-set

名称目标数据类型
tag-set

逗号分隔的字符串集合

csv-set 要求相同。

:其校验逻辑见 SimpleStdDomainHandlers.TagSetType#parseProp

filter-bean

名称目标数据类型
filter-bean

过滤条件

由过滤运算符标签组成的过滤条件,其为 xml 结构,并且条件可嵌套,如:

<and>
<eq name="status" value="1" />
<gt name="age" value="10" />
</and>

field-selection

名称目标数据类型
field-selection

GraphQL 字段选择集合

GraphQL Field Selection 集合,用于指定可返回的字段,如:

userId, userName, status, relatedRoleList{ roleId, roleName, permissionList{ id, name } }

json

名称目标数据类型
json

xjson

名称目标数据类型
xjson

xjson-list

名称目标数据类型
xjson-list

xjson-map

名称目标数据类型
xjson-map

xjson-node

名称目标数据类型
xjson-node

xml

名称目标数据类型
xml

xml-body

名称目标数据类型
xml-body

附录