///|
/// toRDF 处理接口 —— 手写实现(J3 落码;J1 定契约)
/// 契约面与 jsonld_gen.toml 的 handler_hook 对应(接口一致门钉住)。
pub(open) trait ToRdfProcessor {
/// 展开后的节点对象图 → 四元组(@graph 图名与数据集处理在此)
fn node_to_quads(
Self,
expanded : Array[ExpandedValue],
options : JsonLdOptions,
) -> Result[Array[JsonLdQuad], JsonLdError]
}
///|
/// RDF 关键字词表(REC §6 发射位)
const RDF_TYPE_IRI : String = ""
///|
const RDF_FIRST_IRI : String = ""
///|
const RDF_REST_IRI : String = ""
///|
const RDF_NIL_IRI : String = ""
///|
const XSD_BOOLEAN : String = "http://www.w3.org/2001/XMLSchema#boolean"
///|
const XSD_INTEGER : String = "http://www.w3.org/2001/XMLSchema#integer"
///|
const XSD_DOUBLE : String = "http://www.w3.org/2001/XMLSchema#double"
///|
const XSD_STRING : String = "http://www.w3.org/2001/XMLSchema#string"
///|
const RDF_JSON_IRI : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON"
///|
const I18N_PREFIX : String = "https://www.w3.org/ns/i18n#"
///|
const RDF_VALUE_IRI : String = ""
///|
const RDF_LANGUAGE_IRI : String = ""
///|
const RDF_DIRECTION_IRI : String = ""
///|
/// toRDF 落码状态(J3.1——REC §6 对应物):bnode 序号 / emit-once 账 / 产物
priv struct ToRdfState {
mut counter : Int
emitted : Map[String, Bool]
quads : Array[JsonLdQuad]
mut reserved : Map[String, Bool]
/// J3.4:rdfDirection 模式(None/"i18n-datatype"/"compound-literal")
direction_mode : String?
/// J3.4:广义三元组放行(谓词位 bnode)
generalized : Bool
}
///|
/// 词面转义(N-Quads 行结构保持所需最小集:\\ \" \n \r \t)。判定器
/// (parse_nquads)双端同用 canonical 归一 ⇒ 其余控制符词形与判定解耦。
fn nq_emit_escape(value : String) -> String {
let out = StringBuilder()
for ch in value {
match ch {
'\\' => out.write_string("\\\\")
'"' => out.write_string("\\\"")
'\n' => out.write_string("\\n")
'\r' => out.write_string("\\r")
'\t' => out.write_string("\\t")
_ => out.write_char(ch)
}
}
out.to_string()
}
///|
/// IRI 词面合法性(发射面;expand 面不受扰——分层归因,nq 发射层专用):
/// ① 平面门(B组②——RFC 3986/3987 文法外字符弃——#tli12 oracle:
/// "http://invalid/<>/test" 含 `<`/`>` ⇒ 三元组不产出);
/// ② 成分门(C组——spec 附录 IRI 正则:fragment 内不容第二 `#`——
/// #te111/#te112 oracle:vocab `…/rel2##fragment-works` 弃发,
/// `…/rel2#?query=works` 与 `…/rel2#../parent` 保留)
fn nq_iri_chars_legal(iri : String) -> Bool {
let mut in_fragment = false
for ch in iri {
if ch == '#' {
// 成分门:首个 "#" 开 fragment,fragment 内第二 "#" 非法(REC IRI 正则)
if in_fragment {
return false
}
in_fragment = true
} else {
let ok = (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
ch == '-' ||
ch == '.' ||
ch == '_' ||
ch == '~' ||
ch == ':' ||
ch == '/' ||
ch == '?' ||
ch == '[' ||
ch == ']' ||
ch == '@' ||
ch == '!' ||
ch == '$' ||
ch == '&' ||
ch == '\'' ||
ch == '(' ||
ch == ')' ||
ch == '*' ||
ch == '+' ||
ch == ',' ||
ch == ';' ||
ch == '=' ||
ch == '%'
if !ok {
return false
}
}
}
true
}
///|
/// IRI 词面(绝对且字符合法 ⇒ ``;相对/空/文法外字符 ⇒ None——REC §6.3
/// 非绝对主体/谓词不产出)
fn nq_iri_term(iri : String) -> String? {
if is_valid_absolute_iri(iri) && nq_iri_chars_legal(iri) {
Some("<" + iri + ">")
} else {
None
}
}
///|
/// 新生 bnode(序号跳过在册标签——预扫保留集 + 已发射键)
fn nq_fresh_bnode(state : ToRdfState) -> String {
let mut term = "_:b\{state.counter}"
state.counter += 1
while state.reserved.contains(term) {
term = "_:b\{state.counter}"
state.counter += 1
}
term
}
///|
/// 预扫走树:收集全部显式 bnode id(防新生撞号——#t0119 oracle)
fn nq_reserve_labels(value : ExpandedValue, into : Map[String, Bool]) -> Unit {
match value {
ExpandedValue::Node(node) => {
match node.id {
Some(id) => if nq_term_is_bnode(id) { into[id] = true }
None => ()
}
for pair in node.properties {
for v in pair.1 {
nq_reserve_labels(v, into)
}
}
for pair in node.reverse_props {
for v in pair.1 {
nq_reserve_labels(v, into)
}
}
match node.graph {
Some(items) =>
for item in items {
nq_reserve_labels(item, into)
}
None => ()
}
match node.included {
Some(incs) =>
for inc in incs {
nq_reserve_labels(ExpandedValue::Node(inc), into)
}
None => ()
}
}
ExpandedValue::List(items, ..) =>
for item in items {
nq_reserve_labels(item, into)
}
ExpandedValue::Value(_) => ()
}
}
///|
/// 同 id 节点预合并(REC §6.3 同 id 节点属性并集——#tin06/#te014/#te108
/// oracle):**图作用域键**(graph_key|id)——默认图/各具名图分别合并(跨图
/// 不并,#t0027 oracle:Paris#this 两图各发其属性)。首见对象**原地并集**
/// (types/properties/reverse 拼接、graph/included 拼接);重复对象留树中由
/// emit-once 跳过(其贡献已并入首见)。匿名图节点(无 id 的图载体)按出现
/// 序唯一化,互不合并。
priv struct MergeCtx {
mut anon : Int
seen : Map[String, ExpandedNode]
}
///|
fn nq_merge_same_id(
value : ExpandedValue,
ctx : MergeCtx,
graph_key : String,
) -> Result[Unit, JsonLdError] {
match value {
ExpandedValue::Node(node) => {
let self_key : String? = match node.id {
Some(id) =>
if id == EXPLICIT_NULL_ID {
None
} else if nq_term_is_bnode(id) || is_valid_absolute_iri(id) {
Some(graph_key + "|" + id)
} else {
None
}
None => None
}
match self_key {
Some(key) =>
match ctx.seen.get(key) {
Some(first) => {
// @index 冲突(REC Generate Node Map 合并面——#te001 oracle:
// 同 id 双 @index 不等 = colliding indexes 错;缺席侧采纳)
match (first.index, node.index) {
(Some(a), Some(b)) =>
if a != b {
return Err(JsonLdError::InvalidValue("colliding indexes"))
}
(None, Some(b)) => first.index = Some(b)
_ => ()
}
// 原地并集进首见对象
for t in node.types {
first.types.push(t)
}
for p in node.properties {
first.properties.push(p)
}
for p in node.reverse_props {
first.reverse_props.push(p)
}
match node.graph {
Some(items) =>
match first.graph {
Some(first_items) =>
for it in items {
first_items.push(it)
}
None => first.graph = Some(items)
}
None => ()
}
match node.included {
Some(incs) =>
match first.included {
Some(first_incs) =>
for inc in incs {
first_incs.push(inc)
}
None => first.included = Some(incs)
}
None => ()
}
}
None => ctx.seen[key] = node
}
None => ()
}
// 子树续走:图内容子树键 = 本节点词面(具名图作用域);其余继承
for pair in node.properties {
for v in pair.1 {
match nq_merge_same_id(v, ctx, graph_key) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
}
for pair in node.reverse_props {
for v in pair.1 {
match nq_merge_same_id(v, ctx, graph_key) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
}
match node.graph {
Some(items) => {
let inner_key = match self_key {
Some(k) => k
None => graph_key + "|anon\{ctx.anon}"
}
if self_key is None {
ctx.anon += 1
}
for it in items {
match nq_merge_same_id(it, ctx, inner_key) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
}
None => ()
}
match node.included {
Some(incs) =>
for inc in incs {
match nq_merge_same_id(ExpandedValue::Node(inc), ctx, graph_key) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
None => ()
}
Ok(())
}
ExpandedValue::List(items, ..) => {
for it in items {
match nq_merge_same_id(it, ctx, graph_key) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
Ok(())
}
ExpandedValue::Value(_) => Ok(())
}
}
///|
/// 节点主体词面(REC §6.3):无 id / @id 被忽略哨兵(EXPLICIT_NULL_ID)⇒
/// 新生 bnode 并**回写 node.id**(emit-once 键 = 主体词面);`_:` bnode 保留;
/// 相对 id ⇒ None(该节点整体不产出)。
fn nq_node_subject(state : ToRdfState, node : ExpandedNode) -> String? {
match node.id {
None => {
let term = nq_fresh_bnode(state)
node.id = Some(term)
Some(term)
}
Some(id) =>
if id == EXPLICIT_NULL_ID {
// @id 被忽略(keyword 形非关键字——REC §5.2 返回 null)⇒ toRdf 面
// 节点整体不产出(#te122 oracle:ignoreme 引用三元组亦不发射)
None
} else if id.get_char(0) == Some('_') && id.get_char(1) == Some(':') {
Some(id)
} else if is_valid_absolute_iri(id) && nq_iri_chars_legal(id) {
Some("<" + id + ">")
} else {
None
}
}
}
///|
/// 字面量词面(REC §6.4):显式 datatype ⇒ `^^`(相对 datatype ⇒ None
/// 三元组弃;xsd:string 无语言 ⇒ 简单字面量——RDF 1.1 同一性);数值/布尔 ⇒
/// 由 raw 判形(boolean/integer/double——**典范词形 J3.2**,本批词形直出);
/// 语言 ⇒ `@lang`(小写归一);direction 无选项面 ⇒ 弃(J3.4 rdfDirection)。
fn nq_literal_term_of(state : ToRdfState, l : ExpandedLiteral) -> String? {
let esc = nq_emit_escape(l.value)
// J3.4 i18n 面(REC §6.4——spec §8.36):值对象带方向 + rdfDirection=
// i18n-datatype ⇒ datatype = i18n#{lang}_{dir}(语言小写化;无语言 = "_"
// 空段——tdi09/tdi10 oracle);值原样、无 langtag
if l.direction is Some(_) && state.direction_mode is Some("i18n-datatype") {
let lang_part = match l.language {
Some(lang) => nq_lowercase(lang)
None => ""
}
let dir_part = match l.direction {
Some(d) => d
None => ""
}
return Some(
"\"" + esc + "\"^^<" + I18N_PREFIX + lang_part + "_" + dir_part + ">",
)
}
match l.datatype {
Some("@json") =>
// J3.3(JCS——RFC 8785):raw JSON → 规范词形 → rdf:JSON 字面量
//(#tjs01~23 oracle;expand 侧 datatype 哨兵 "@json" 在此展开成真 IRI)
match l.raw {
Some(jv) =>
Some("\"" + nq_emit_escape(nq_jcs(jv)) + "\"^^<" + RDF_JSON_IRI + ">")
None => None
}
Some(dt) => {
// raw-Number 子例(J3.2):coercion 位数值亦典范化——xsd:double 强制
// double 形(#t0035 oracle:1 → 1.0E0);自定义 datatype 跟值形态
//(#te061 oracle:1→"1"、5.1→5.1E0)
let value_form = match l.raw {
Some(JsonValue::Number(_)) =>
nq_canonical_number(l.value, dt == XSD_DOUBLE).0
_ => esc
}
if dt == XSD_STRING && l.language is None {
Some("\"" + value_form + "\"")
} else if is_valid_absolute_iri(dt) && nq_iri_chars_legal(dt) {
Some("\"" + value_form + "\"^^<" + dt + ">")
} else {
None
}
}
None =>
match l.language {
Some(lang) =>
if nq_valid_langtag(lang) {
Some("\"" + esc + "\"@" + nq_lowercase(lang))
} else {
None
}
None =>
match l.raw {
Some(JsonValue::Bool(b)) =>
Some("\"" + "\{b}" + "\"^^<" + XSD_BOOLEAN + ">")
Some(JsonValue::Number(_)) => {
// 数词典范形(J3.2——REC §6.4):值整 + |v|<1e21 ⇒ integer,
// 否则 double;词形十进制串算术典范化(词形直出退役——
// #t0022/#trt01 oracle:5.3→5.3E0、-0e0→0、1e21→1.0E21)
let (form, is_double) = nq_canonical_number(l.value, false)
let dt = if is_double { XSD_DOUBLE } else { XSD_INTEGER }
Some("\"" + form + "\"^^<" + dt + ">")
}
_ => Some("\"" + esc + "\"")
}
}
}
}
///|
/// JSON 数词典范形(J3.2——REC §6.4;十进制串算术,不涉浮点打印):值整 +
/// |v| < 1e21 ⇒ integer 典范形,否则 double 典范形。forced = xsd:double
/// datatype 强制 double 形。返回(词形,double 形?)。(#t0022/#t0035/
/// #te031/#te061/#trt01 oracle:5.3→5.3E0、1→1.0E0〔forced〕、123.45→
/// 1.2345E2、-0e0→0、1e21→1.0E21、8→8)
fn nq_canonical_number(lexeme : String, forced_double : Bool) -> (String, Bool) {
let len = lexeme.length()
let mut i = 0
let mut neg = false
if i < len && lexeme.get_char(i) == Some('-') {
neg = true
i += 1
}
let digits = StringBuilder()
let mut frac_len = 0
let mut seen_point = false
while i < len {
let ch = lexeme.get_char(i)
if ch == Some('.') {
seen_point = true
i += 1
} else if ch is Some(c) && c >= '0' && c <= '9' {
digits.write_char(c)
if seen_point {
frac_len += 1
}
i += 1
} else {
break
}
}
let mut exp = 0
if i < len &&
(lexeme.get_char(i) == Some('e') || lexeme.get_char(i) == Some('E')) {
i += 1
let mut eneg = false
if i < len &&
(lexeme.get_char(i) == Some('+') || lexeme.get_char(i) == Some('-')) {
eneg = lexeme.get_char(i) == Some('-')
i += 1
}
let mut ev = 0
while i < len {
match lexeme.get_char(i) {
Some(c) =>
if c >= '0' && c <= '9' {
ev = ev * 10 + (c.to_int() - '0'.to_int())
i += 1
} else {
break
}
None => break
}
}
exp = if eneg { -ev } else { ev }
}
// 有效位(去前导零;全零 ⇒ 零值)
let mut sig = digits.to_string()
if sig is "" {
sig = "0"
}
let mut head = 0
while head < sig.length() - 1 && sig.get_char(head) == Some('0') {
head += 1
}
let sig = sig[head:].to_owned()
let all_zero = sig.get_char(0) == Some('0') && sig.length() == 1
let shift = exp - frac_len // 值 = sig × 10^shift
// 整形臂:值整(shift ≥ 0 或 sig 尾零足够)且 |v| < 1e21(典范整数 ≤ 21 位)
if !forced_double && !all_zero {
let mut int_digits = ""
let integral = if shift >= 21 {
false // sig.length() ≥ 1 ⇒ |v| ≥ 1e21
} else if shift >= 0 {
let buf = StringBuilder()
buf.write_string(sig)
for _ in 0.. -shift {
let mut tail_zero = true
for j in (sig.length() + shift).. 1 && mant.get_char(mant.length() - 1) == Some('0') {
mant = mant[:mant.length() - 1].to_owned()
}
let k = shift + sig.length() - 1
let mant_head = mant[0:1].to_owned()
let mant_tail = if mant.length() > 1 { mant[1:].to_owned() } else { "0" }
let sign_str = if neg { "-" } else { "" }
("\{sign_str}\{mant_head}.\{mant_tail}E\{k}", true)
}
///|
/// 语言标签良构校验(RFC 3066 形——REC §6.4):段 '-' 分隔、首段全 ALPHA
/// (1..8)、余段字母数字(1..8);非法 ⇒ 三元组弃(#twf05 oracle:"a b" →
/// 空 dataset)
fn nq_valid_langtag(tag : String) -> Bool {
let mut seg_len = 0
let mut seg_index = 0
for ch in tag {
if ch == '-' {
if seg_len == 0 || seg_len > 8 {
return false
}
seg_index += 1
seg_len = 0
} else {
let is_alpha = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
let is_digit = ch >= '0' && ch <= '9'
let ok = if seg_index == 0 { is_alpha } else { is_alpha || is_digit }
if !ok {
return false
}
seg_len += 1
if seg_len > 8 {
return false
}
}
}
seg_len > 0 && seg_len <= 8
}
///|
/// 词面是否 bnode("_:" 前缀——主包侧判定件,判定器同形)
fn nq_term_is_bnode(term : String) -> Bool {
term.get_char(0) == Some('_') && term.get_char(1) == Some(':')
}
///|
/// 语言标签小写归一(RDF 语言标签大小写不敏感——README 判定注记;判定器
/// parse_nquads 同步归一,双端一致)
fn nq_lowercase(tag : String) -> String {
let out = StringBuilder()
for ch in tag {
if ch >= 'A' && ch <= 'Z' {
out.write_char((ch.to_int() + 32).to_char().unwrap())
} else {
out.write_char(ch)
}
}
out.to_string()
}
///|
/// 十六进制位(JCS 转义 <0x20 用——入参受控 0..15)
fn nq_hex_char(d : Int) -> Char {
(if d < 10 { '0'.to_int() + d } else { 'a'.to_int() + d - 10 })
.to_char()
.unwrap()
}
///|
/// JCS 字符串转义(RFC 8785 §3.2.2.2):短转义 \" \\ \b \t \n \f \r +
/// 其余 <0x20 用 \u00xx 小写十六进制;余字面 UTF-8 直通。
fn nq_jcs_escape(text : String) -> String {
let out = StringBuilder()
for ch in text {
match ch {
'"' => out.write_string("\\\"")
'\\' => out.write_string("\\\\")
'\u{8}' => out.write_string("\\b")
'\t' => out.write_string("\\t")
'\n' => out.write_string("\\n")
'\u{c}' => out.write_string("\\f")
'\r' => out.write_string("\\r")
_ =>
if ch.to_int() < 0x20 {
let v = ch.to_int()
out.write_string("\\u00")
out.write_char(nq_hex_char(v / 16))
out.write_char(nq_hex_char(v % 16))
} else {
out.write_char(ch)
}
}
}
out.to_string()
}
///|
/// JCS 数组(逗号连接,无空格)
fn nq_jcs_array(items : Array[JsonValue]) -> String {
let parts = StringBuilder()
parts.write_string("[")
for i, item in items {
if i > 0 {
parts.write_string(",")
}
parts.write_string(nq_jcs(item))
}
parts.write_string("]")
parts.to_string()
}
///|
/// JCS 键序比较器(Unicode 码点序——#tjs13 oracle:U+F8DF < U+1F602;
/// String::compare 为长度优先假序,不可用——tjs09 实证 sin/peach 逆序)
fn nq_key_compare(a : String, b : String) -> Int {
let ca : Array[Char] = []
for ch in a {
ca.push(ch)
}
let cb : Array[Char] = []
for ch in b {
cb.push(ch)
}
let n = if ca.length() < cb.length() { ca.length() } else { cb.length() }
for i in 0.. String {
let sorted = pairs.map(fn(pair) { (pair.0, pair.1) })
sorted.sort_by(fn(a, b) { nq_key_compare(a.0, b.0) })
let parts = StringBuilder()
parts.write_string("{")
for i, pair in sorted {
if i > 0 {
parts.write_string(",")
}
parts.write_string("\"")
parts.write_string(nq_jcs_escape(pair.0))
parts.write_string("\":")
parts.write_string(nq_jcs(pair.1))
}
parts.write_string("}")
parts.to_string()
}
///|
/// JCS 规范序列化(RFC 8785;J3.3——#tjs01~23 oracle):数值词形 =
/// ECMAScript Number::toString——MoonBit Double 显示即该形(探针实证:
/// "1e+30"/"0.002"/"1e-7"/负零 → "0")。
fn nq_jcs(value : JsonValue) -> String {
match value {
JsonValue::Null => "null"
JsonValue::Bool(b) => if b { "true" } else { "false" }
JsonValue::Number(nv) => "\{nv}"
JsonValue::String(sv) => "\"" + nq_jcs_escape(sv) + "\""
JsonValue::Array(items) => nq_jcs_array(items)
JsonValue::Object(pairs) => nq_jcs_object(pairs)
}
}
///|
/// JCS 规范化序列化出口(RFC 8785;评审役 R-1 处置,2026-10-09 晋 pub):
/// 键 = Unicode 码点序(nq_key_compare)、字符串转义 §3.2.2.2、数值词形 =
/// ECMAScript Number::toString——本体 nq_jcs(J3.3,#tjs01~23 oracle 全绿)。
/// 与 json_canonical(对拍归一器,比对用)分账:对外规范化序列化用本品。
pub fn jcs_serialize(value : JsonValue) -> String {
nq_jcs(value)
}
///|
/// 带方向值对象的 compound-literal 面(REC §6.4——spec §8.36):对象换新
/// bnode + rdf:value(**恒简单字面量**,语言不进 value——tdi12 oracle)+
/// rdf:language(小写)/ rdf:direction;无方向或非 compound 模式 ⇒ 常规
/// 字面量词面。
fn nq_directional_literal_term(
state : ToRdfState,
l : ExpandedLiteral,
graph : String?,
) -> String? {
if l.direction is Some(_) && state.direction_mode is Some("compound-literal") {
let b = nq_fresh_bnode(state)
nq_emit(
state,
b,
RDF_VALUE_IRI,
"\"" + nq_emit_escape(l.value) + "\"",
graph,
)
match l.language {
Some(lang) =>
nq_emit(
state,
b,
RDF_LANGUAGE_IRI,
"\"" + nq_lowercase(lang) + "\"",
graph,
)
None => ()
}
match l.direction {
Some(d) =>
nq_emit(
state,
b,
RDF_DIRECTION_IRI,
"\"" + nq_emit_escape(d) + "\"",
graph,
)
None => ()
}
Some(b)
} else {
nq_literal_term_of(state, l)
}
}
///|
/// 值 → 对象词面(节点 = 主体词面 + 递归;字面量;@list = 链头)
fn nq_value_term(
state : ToRdfState,
value : ExpandedValue,
graph : String?,
) -> String? {
match value {
ExpandedValue::Node(child) =>
match nq_node_subject(state, child) {
Some(term) => {
nq_node_to_rdf(state, child, graph)
Some(term)
}
None => None
}
ExpandedValue::Value(l) => nq_directional_literal_term(state, l, graph)
ExpandedValue::List(items, ..) => Some(nq_list_to_rdf(state, items, graph))
}
}
///|
/// @list → rdf:first/rdf:rest 链(空 ⇒ rdf:nil;嵌套 list 递归成子链)
fn nq_list_to_rdf(
state : ToRdfState,
items : Array[ExpandedValue],
graph : String?,
) -> String {
if items.length() == 0 {
return RDF_NIL_IRI
}
let nodes : Array[String] = []
for _item in items {
let b = "_:b\{state.counter}"
state.counter += 1
nodes.push(b)
}
for i, item in items {
let next = if i + 1 < items.length() { nodes[i + 1] } else { RDF_NIL_IRI }
match nq_value_term(state, item, graph) {
Some(obj) => nq_emit(state, nodes[i], RDF_FIRST_IRI, obj, graph)
None => ()
}
nq_emit(state, nodes[i], RDF_REST_IRI, next, graph)
}
nodes[0]
}
///|
/// 三元组发射(图位可空 = 默认图)
fn nq_emit(
state : ToRdfState,
subject : String,
predicate : String,
object : String,
graph : String?,
) -> Unit {
state.quads.push({ subject, predicate, object, graph, })
}
///|
/// 节点对象 → 四元组(REC §6.3):emit-once(主体词面为键——bnode 回写后
/// 引用共享即去重);types ⇒ rdf:type;属性值三分(节点/值对象/list);
/// reverse ⇒ 反向三元组;节点位 @graph ⇒ 具名图(图名 = 本节点主体);
/// included ⇒ 同图续展开。
fn nq_node_to_rdf(
state : ToRdfState,
node : ExpandedNode,
graph : String?,
) -> Unit {
let subject = match nq_node_subject(state, node) {
Some(s) => s
None => return
}
// emit-once 键含图位(#t0027/#te108 oracle:同节点对象在两图各发一次属性)
let emit_key = match graph {
Some(g) => "\{g}|\{subject}"
None => subject
}
if state.emitted.contains(emit_key) {
return
}
state.emitted[emit_key] = true
for t in node.types {
// bnode 标签型 @type(@type map 的 "_:bar" 条目——#tm003/#tm004 oracle)
// 保留词面作 bnode 主体;其余须绝对 IRI
if nq_term_is_bnode(t) {
nq_emit(state, subject, RDF_TYPE_IRI, t, graph)
} else {
match nq_iri_term(t) {
Some(ti) => nq_emit(state, subject, RDF_TYPE_IRI, ti, graph)
None => ()
}
}
}
for pair in node.properties {
let p = match nq_iri_term(pair.0) {
Some(p) => p
None =>
// J3.4 generalized 面:谓词位 bnode 放行(#t0118/#te075 oracle)
if state.generalized && nq_term_is_bnode(pair.0) {
pair.0
} else {
continue
}
}
for v in pair.1 {
match nq_value_term(state, v, graph) {
Some(obj) => nq_emit(state, subject, p, obj, graph)
None => ()
}
}
}
// reverse ⇒ 反向三元组(值主体作 subject——@reverse 语义)
for pair in node.reverse_props {
let p = match nq_iri_term(pair.0) {
Some(p) => p
None =>
// J3.4 generalized 面:谓词位 bnode(reverse 同权)
if state.generalized && nq_term_is_bnode(pair.0) {
pair.0
} else {
continue
}
}
for v in pair.1 {
match v {
ExpandedValue::Node(child) =>
match nq_node_subject(state, child) {
Some(ct) => {
nq_emit(state, ct, p, subject, graph)
nq_node_to_rdf(state, child, graph)
}
None => ()
}
_ => ()
}
}
}
// 节点位 @graph ⇒ 具名图(图名 = 本节点主体;子节点递归入该图)
match node.graph {
Some(items) =>
for item in items {
match item {
ExpandedValue::Node(child) =>
nq_node_to_rdf(state, child, Some(subject))
_ => ()
}
}
None => ()
}
// included ⇒ 同图续展开(#tin03 族——included 是完整节点对象)
match node.included {
Some(incs) =>
for inc in incs {
nq_node_to_rdf(state, inc, graph)
}
None => ()
}
}
///|
/// toRDF 落码(J3.1——REC §6 对应物;批边界见 spec §8):展开形 → 四元组。
/// **批内**:串/语言/显式 datatype/bnode(含回写 emit-once)/rdf:type/
/// @list 链/具名图 + 默认图/反向。**后续批**:数值典范形(J3.2——raw 词形
/// 直出)、@json→rdf:JSON+JCS(J3.3)、rdfDirection(J3.4,本批 direction 弃)。
impl ToRdfProcessor for StandardExpander with fn node_to_quads(
_self,
expanded : Array[ExpandedValue],
options : JsonLdOptions,
) -> Result[Array[JsonLdQuad], JsonLdError] {
let reserved : Map[String, Bool] = Map([])
let state : ToRdfState = {
counter: 0,
emitted: Map([]),
quads: [],
reserved,
direction_mode: options.rdf_direction,
generalized: options.produce_generalized_rdf,
}
// bnode 标签预扫保留(#t0119 oracle:输入明写 "_:b0" 与新生 "_:b0" 撞号
// ⇒ 同体合并假自指;新生序号跳过全部在册标签)
let reserved : Map[String, Bool] = Map([])
for value in expanded {
nq_reserve_labels(value, reserved)
}
state.reserved = reserved
// 同 id 节点预合并(图作用域——F2;@index 冲突同点检出)
let merge_ctx : MergeCtx = { anon: 0, seen: Map([]), }
for value in expanded {
match nq_merge_same_id(value, merge_ctx, "") {
Err(e) => return Err(e)
Ok(_) => ()
}
}
for value in expanded {
match value {
ExpandedValue::Node(node) => nq_node_to_rdf(state, node, None)
_ => ()
}
}
// RDF 集合语义:重复三元组塌缩(#ttn02 oracle——同型 bool 值对象 ×2 → ×1)
let seen : Map[String, Bool] = Map([])
let out : Array[JsonLdQuad] = []
for q in state.quads {
let key = match q.graph {
Some(g) => "\{q.subject} \{q.predicate} \{q.object} \{g}"
None => "\{q.subject} \{q.predicate} \{q.object}"
}
if !seen.contains(key) {
seen[key] = true
out.push(q)
}
}
Ok(out)
}