code wiki / _hdl_build / nx_entity_card.nx
nx_entity_card.nx source
↩ module page · 153 lines · 10312 B
1// nx_entity_card.nx -- KNOWLEDGE PANEL / entity card (the #1 returns-quality gap, returns-census 2026-07-06).
2// SOTA search shows a photo+bio+facts CARD for a person/entity query; we showed a link list. This extracts a
3// card from a page's structured data -- og/JSON-LD name+description+image+type (via nx_struct_extract) PLUS
4// JSON-LD Person facts (birthDate/birthPlace/jobTitle/nationality) -- and renders it. Composes the SAME
5// extractors nx_url_index already runs at index time, so wiring this into the serve is free data.
6// usage: nx_entity_card <url>
7// license_tier: ORIGINAL
8import "nx_struct_extract.nx" // nx_struct_extract + se_jsonld + se_meta (og/twitter/JSON-LD)
9import "nx_x509_trust_store.nx"
10import "nx_trust_store_load_from_certdata.nx"
11import "nx_https_fetch_follow.nx"
12const K_MAGIC_4194304: i64 = 4194304
13const K_MAGIC_1024: i64 = 1024
14const K_MAGIC_2048: i64 = 2048
15
16func ec_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
17func ec_find(hay: *u8, from: i64, hlen: i64, ndl: *u8, nlen: i64) -> i64 {
18 var i: i64 = from
19 while i + nlen <= hlen { var m: i64=1; var j: i64=0; while j<nlen { if ec_lc(hay[i+j] as i64)!=ec_lc(ndl[j] as i64) { m=0; j=nlen } else { j=j+1 } } if m==1 { return i } i=i+1 }
20 return 0 - 1
21}
22// strip HTML tags + collapse whitespace from html[s,e) -> out. returns len.
23func ec_strip(html: *u8, s: i64, e: i64, out: *u8, cap: i64) -> i64 {
24 var o: i64=0; var i: i64=s; var intag: i64=0; var lastsp: i64=0
25 while i < e {
26 let c: i64 = html[i] as i64
27 if c==60 { intag=1 } else { if c==62 { intag=0 } else { if intag==0 {
28 var ch: i64=c; if ch==9 { ch=32 } if ch==10 { ch=32 } if ch==13 { ch=32 } if ch==160 { ch=32 }
29 if ch==32 { if lastsp==0 { if o<cap-1 { out[o]=32 as u8; o=o+1 } lastsp=1 } } else { if o<cap-1 { out[o]=ch as u8; o=o+1 } lastsp=0 } } } }
30 i=i+1
31 }
32 out[o]=0 as u8; return o
33}
34// wikipedia INFOBOX field: within the infobox table, find ">LABEL<" (a th/td label) then the next <td value,
35// strip it. This is where wikipedia keeps person facts (Born/Occupation/Years active) -- NOT in JSON-LD.
36func ec_infobox(html: *u8, n: i64, label: *u8, llen: i64, out: *u8, cap: i64) -> i64 {
37 out[0]=0 as u8
38 let ibx: i64 = ec_find(html, 0, n, "class=\"infobox" as *u8, 14)
39 if ibx < 0 { return 0 - 1 }
40 // needle = ">" + label + "<"
41 let ndl: *u8 = sys_mmap(64); ndl[0]=62 as u8; var t: i64=0; while t<llen { ndl[1+t]=label[t]; t=t+1 } ndl[1+llen]=60 as u8
42 let lp: i64 = ec_find(html, ibx, n, ndl, llen+2)
43 if lp < 0 { return 0 - 1 }
44 let td: i64 = ec_find(html, lp, n, "<td" as *u8, 3)
45 if td < 0 { return 0 - 1 }
46 var gt: i64 = td+3; var f: i64=0
47 while f==0 { if gt>=n { return 0-1 } if html[gt]==(62 as u8) { f=1 } else { gt=gt+1 } }
48 let cl: i64 = ec_find(html, gt, n, "</td" as *u8, 4)
49 if cl < 0 { return 0 - 1 }
50 return ec_strip(html, gt+1, cl, out, cap)
51}
52// decode &#NN; < > & "   etc. in place-ish (inb -> out).
53func ec_decode(inb: *u8, out: *u8, cap: i64) -> i64 {
54 var o: i64=0; var i: i64=0
55 while inb[i] != (0 as u8) {
56 if inb[i]==(38 as u8) {
57 var semi: i64=0-1; var s: i64=i+1
58 while s < i+12 { if inb[s]==(0 as u8) { s=i+100 } else { if inb[s]==(59 as u8) { semi=s; s=i+100 } else { s=s+1 } } }
59 if semi<0 { if o<cap-1 { out[o]=38 as u8; o=o+1 } i=i+1 } else {
60 var emit: i64=0-1
61 if inb[i+1]==(35 as u8) { var v: i64=0; var p: i64=i+2; while p<semi { let d: i64=inb[p] as i64; if d>=48 { if d<=57 { v=v*10+(d-48) } } p=p+1 } if v>0 { if v<128 { emit=v } else { emit=32 } } }
62 else { let nl: i64=semi-i-1
63 if nl==2 { if ec_lc(inb[i+1] as i64)==108 { if ec_lc(inb[i+2] as i64)==116 { emit=60 } } if ec_lc(inb[i+1] as i64)==103 { if ec_lc(inb[i+2] as i64)==116 { emit=62 } } }
64 if nl==3 { emit=38 } // amp
65 if nl==4 { if ec_lc(inb[i+1] as i64)==113 { emit=34 } if ec_lc(inb[i+1] as i64)==97 { emit=39 } } }
66 if emit>=0 { if o<cap-1 { out[o]=emit as u8; o=o+1 } i=semi+1 } else { if o<cap-1 { out[o]=38 as u8; o=o+1 } i=i+1 } }
67 } else { if o<cap-1 { out[o]=inb[i]; o=o+1 } i=i+1 }
68 }
69 out[o]=0 as u8; return o
70}
71// clean a page-title into an entity name: cut at the first " - "/" | "/" ⋆ "/" &" site-suffix separator.
72func ec_cleanname(nm: *u8) -> i64 {
73 var i: i64=0
74 while nm[i] != (0 as u8) {
75 if nm[i]==(32 as u8) { let c2: i64=nm[i+1] as i64
76 if c2==45 { if nm[i+2]==(32 as u8) { nm[i]=0 as u8; return 0 } } // " - "
77 if c2==124 { nm[i]=0 as u8; return 0 } // " |"
78 if c2==(0xe2) { nm[i]=0 as u8; return 0 } // " ⋆" (utf8 star lead byte)
79 }
80 i=i+1
81 }
82 return 0
83}
84func ec_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
85func ec_wb(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 }
86func ec_sl(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
87// print a labelled field only if non-empty
88func ec_field(label: *u8, val: *u8) -> i64 {
89 if val[0] == (0 as u8) { return 0 }
90 ec_w(" "); ec_w(label); ec_w(ec_trunc_note(val)); ec_w("\n" as *u8); return 0
91}
92func ec_trunc_note(v: *u8) -> *u8 { return v } // (identity; truncation handled by callers)
93
94func main(argc: i64, argv: *i64) -> i64 {
95 if argc < 2 { ec_w("usage: nx_entity_card <url>\n" as *u8); return 1 }
96 let url: *u8 = argv[1] as *u8
97 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, K_MAGIC_4194304)
98 if r <= 0 { ec_w("trust store load failed\n" as *u8); return 2 }
99 let store: *TrustStore = r as *TrustStore
100 let cap: i64 = K_MAGIC_4194304
101 let out: *u8 = sys_mmap(cap)
102 let st: *i64 = sys_mmap(8) as *i64
103 let n: i64 = nx_https_fetch_follow_best(url, store, out, cap, 6, st)
104 if st[0] != 200 { ec_w("fetch status="); let sb: *u8=sys_mmap(16); var sm: i64=st[0]; var sk: i64=0; if sm==0 {sb[0]=48 as u8; sk=1} while sm>0 {sb[sk]=(48+(sm%10)) as u8; sm=sm/10; sk=sk+1} var sq: i64=sk-1; while sq>=0 {sys_write(1,(sb as i64+sq) as *u8,1); sq=sq-1} ec_w(" (not 200)\n" as *u8); return 3 }
105
106 let name: *u8 = sys_mmap(K_MAGIC_1024); let desc: *u8 = sys_mmap(K_MAGIC_2048)
107 let etype: *u8 = sys_mmap(256); let site: *u8 = sys_mmap(256)
108 nx_struct_extract(out, n, name, K_MAGIC_1024, desc, K_MAGIC_2048, etype, 256, site, 256)
109 let image: *u8 = sys_mmap(K_MAGIC_1024); image[0] = 0 as u8
110 se_meta(out, n, "property" as *u8, 8, "og:image" as *u8, 8, image, K_MAGIC_1024)
111 // JSON-LD Person facts
112 let born: *u8 = sys_mmap(512); born[0]=0 as u8; se_jsonld(out, n, "birthDate" as *u8, 9, born, 512)
113 if born[0]==(0 as u8) { ec_infobox(out, n, "Born" as *u8, 4, born, 512) } // wikipedia infobox fallback
114 let bplace: *u8 = sys_mmap(512); bplace[0]=0 as u8; se_jsonld(out, n, "birthPlace" as *u8, 10, bplace, 512)
115 let job: *u8 = sys_mmap(512); job[0]=0 as u8; se_jsonld(out, n, "jobTitle" as *u8, 8, job, 512)
116 if job[0]==(0 as u8) { ec_infobox(out, n, "Occupation" as *u8, 10, job, 512) }
117 if job[0]==(0 as u8) { ec_infobox(out, n, "Occupations" as *u8, 11, job, 512) }
118 let years: *u8 = sys_mmap(256); years[0]=0 as u8; ec_infobox(out, n, "Years active" as *u8, 12, years, 256)
119 let nat: *u8 = sys_mmap(256); nat[0]=0 as u8; se_jsonld(out, n, "nationality" as *u8, 11, nat, 256)
120 let atype: *u8 = sys_mmap(256); atype[0]=0 as u8; se_jsonld(out, n, "@type" as *u8, 5, atype, 256)
121 // wikipedia bio: if og/jsonld description empty, take the infobox-adjacent... (kept simple: og already tried)
122
123 // POLISH: decode entities + clean name into an entity name; infer Person when infobox has a birth fact
124 let named: *u8 = sys_mmap(K_MAGIC_1024); ec_decode(name, named, K_MAGIC_1024); ec_cleanname(named)
125 let bornd: *u8 = sys_mmap(512); ec_decode(born, bornd, 512)
126 let jobd: *u8 = sys_mmap(512); ec_decode(job, jobd, 512)
127 let descd: *u8 = sys_mmap(K_MAGIC_2048); ec_decode(desc, descd, K_MAGIC_2048)
128 var is_person: i64 = 0; if born[0] != (0 as u8) { is_person = 1 }
129 ec_w("+----------------- KNOWLEDGE PANEL -----------------+\n" as *u8)
130 ec_w(" NAME: "); ec_wb(named, ec_sl(named)); ec_w("\n" as *u8)
131 if is_person == 1 { ec_w(" TYPE: Person\n" as *u8) }
132 else { if atype[0] != (0 as u8) { ec_w(" TYPE: "); ec_wb(atype, ec_sl(atype)); ec_w("\n" as *u8) }
133 else { if etype[0] != (0 as u8) { ec_w(" TYPE: "); ec_wb(etype, ec_sl(etype)); ec_w("\n" as *u8) } } }
134 if bornd[0] != (0 as u8) { var bl: i64=ec_sl(bornd); if bl>120 { bl=120 } ec_w(" BORN: "); ec_wb(bornd, bl); ec_w("\n" as *u8) }
135 if jobd[0] != (0 as u8) { var jl: i64=ec_sl(jobd); if jl>180 { jl=180 } ec_w(" ROLE: "); ec_wb(jobd, jl); ec_w("\n" as *u8) }
136 if years[0] != (0 as u8) { ec_w(" ACTIVE:"); ec_wb(years, ec_sl(years)); ec_w("\n" as *u8) }
137 if nat[0] != (0 as u8) { ec_w(" NATL: "); ec_wb(nat, ec_sl(nat)); ec_w("\n" as *u8) }
138 if descd[0] != (0 as u8) {
139 var dl: i64 = ec_sl(descd); if dl > 300 { dl = 300 }
140 ec_w(" BIO: "); ec_wb(descd, dl); if ec_sl(descd) > 300 { ec_w("..." as *u8) } ec_w("\n" as *u8)
141 }
142 if site[0] != (0 as u8) { ec_w(" SITE: "); ec_wb(site, ec_sl(site)); ec_w("\n" as *u8) }
143 if image[0] != (0 as u8) { ec_w(" PHOTO: "); ec_wb(image, ec_sl(image)); ec_w("\n" as *u8) }
144 ec_w(" SOURCE:"); ec_w(url); ec_w("\n" as *u8)
145 ec_w("+---------------------------------------------------+\n" as *u8)
146 // report which fields populated (card richness)
147 var fields: i64 = 0
148 if name[0]!=(0 as u8) { fields=fields+1 } if desc[0]!=(0 as u8) { fields=fields+1 }
149 if image[0]!=(0 as u8) { fields=fields+1 } if born[0]!=(0 as u8) { fields=fields+1 }
150 if job[0]!=(0 as u8) { fields=fields+1 } if nat[0]!=(0 as u8) { fields=fields+1 }
151 ec_w(" card-fields populated: "); let fb: *u8=sys_mmap(16); var fm: i64=fields; var fk: i64=0; if fm==0 {fb[0]=48 as u8; fk=1} while fm>0 {fb[fk]=(48+(fm%10)) as u8; fm=fm/10; fk=fk+1} var fq: i64=fk-1; while fq>=0 {sys_write(1,(fb as i64+fq) as *u8,1); fq=fq-1} ec_w("/6\n" as *u8)
152 return 0
153}