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; &lt; &gt; &amp; &quot; &#160; 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}