code wiki / (root) / sitemap.nx

sitemap.nx source

↩ module page · 193 lines · 6892 B

1// sitemap.nx -- XML Sitemap 0.9 builder. 2// 3// Specification: sitemaps.org (Google/Yahoo/MSN 2005 joint draft). 4// Every search crawler uses it to discover pages it might not 5// reach via link-following. Referenced from robots.txt and served 6// at /sitemap.xml or /sitemap-index.xml. 7// 8// Skeleton: 9// <?xml version=\"1.0\" encoding=\"utf-8\"?> 10// <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"> 11// <url> 12// <loc>https://example.com/foo</loc> 13// <lastmod>2026-04-22</lastmod> 14// <changefreq>weekly</changefreq> 15// <priority>0.8</priority> 16// </url> 17// ... 18// </urlset> 19// 20// Soft caps (per spec): 50,000 URLs per sitemap, 50 MiB uncompressed. 21// For larger sites use a sitemap index (not implemented here yet; 22// a future sitemap_index.nx will compose this). 23// 24// Invariants: 25// S1 loc is required; lastmod/changefreq/priority optional. 26// S2 lastmod is date-only format YYYY-MM-DD (from unix_sec 27// via civil_date.nx). Sitemaps accept ISO 8601 but 28// date-only is canonical and smaller. 29// S3 changefreq values: always, hourly, daily, weekly, monthly, 30// yearly, never. We surface them as constants; caller picks. 31// S4 Stream builder: sitemap_begin -> sitemap_url* -> sitemap_end. 32 33import "syscalls.nx" 34import "html_escape.nx" 35import "civil_date.nx" 36 37const SM_ERR_SHORT: i64 = -1 38 39const SM_FREQ_ALWAYS: i64 = 1 40const SM_FREQ_HOURLY: i64 = 2 41const SM_FREQ_DAILY: i64 = 3 42const SM_FREQ_WEEKLY: i64 = 4 43const SM_FREQ_MONTHLY: i64 = 5 44const SM_FREQ_YEARLY: i64 = 6 45const SM_FREQ_NEVER: i64 = 7 46const SM_FREQ_NONE: i64 = 0 47 48func sm_put(out: *u8, cap: i64, off: i64, src: *u8, n: i64) -> i64 { 49 if off + n > cap { return SM_ERR_SHORT } 50 var i: i64 = 0 51 while i < n { 52 out[off + i] = src[i] 53 i = i + 1 54 } 55 return off + n 56} 57 58func sm_put_escaped(out: *u8, cap: i64, off: i64, 59 src: *u8, n: i64) -> i64 { 60 let w: i64 = html_escape(out + off, cap - off, src, n) 61 if w < 0 { return SM_ERR_SHORT } 62 return off + w 63} 64 65// Write 4-digit year, 2-digit month, 2-digit day: YYYY-MM-DD. 66func sm_put_date(out: *u8, cap: i64, off: i64, unix_sec: i64) -> i64 { 67 if off + 10 > cap { return SM_ERR_SHORT } 68 let cd_raw: *u8 = sys_mmap(128) 69 let cd: *CivilDate = cd_raw as *CivilDate 70 civil_from_unix(unix_sec, cd) 71 out[off] = 0x30 + ((cd.year / 1000) % 10) 72 out[off + 1] = 0x30 + ((cd.year / 100) % 10) 73 out[off + 2] = 0x30 + ((cd.year / 10) % 10) 74 out[off + 3] = 0x30 + (cd.year % 10) 75 out[off + 4] = 0x2D 76 out[off + 5] = 0x30 + ((cd.month / 10) % 10) 77 out[off + 6] = 0x30 + (cd.month % 10) 78 out[off + 7] = 0x2D 79 out[off + 8] = 0x30 + ((cd.day / 10) % 10) 80 out[off + 9] = 0x30 + (cd.day % 10) 81 return off + 10 82} 83 84// Write a changefreq value based on SM_FREQ_*. Returns new 85// offset or unchanged if freq == SM_FREQ_NONE. 86func sm_put_freq(out: *u8, cap: i64, off: i64, freq: i64) -> i64 { 87 if freq == SM_FREQ_NONE { return off } 88 var cur: i64 = off 89 cur = sm_put(out, cap, cur, "<changefreq>", 12) 90 if cur < 0 { return cur } 91 if freq == SM_FREQ_ALWAYS { cur = sm_put(out, cap, cur, "always", 6) } 92 if freq == SM_FREQ_HOURLY { cur = sm_put(out, cap, cur, "hourly", 6) } 93 if freq == SM_FREQ_DAILY { cur = sm_put(out, cap, cur, "daily", 5) } 94 if freq == SM_FREQ_WEEKLY { cur = sm_put(out, cap, cur, "weekly", 6) } 95 if freq == SM_FREQ_MONTHLY { cur = sm_put(out, cap, cur, "monthly", 7) } 96 if freq == SM_FREQ_YEARLY { cur = sm_put(out, cap, cur, "yearly", 6) } 97 if freq == SM_FREQ_NEVER { cur = sm_put(out, cap, cur, "never", 5) } 98 if cur < 0 { return cur } 99 cur = sm_put(out, cap, cur, "</changefreq>\n", 14) 100 return cur 101} 102 103// Emit opening + urlset. 104func sitemap_begin(out: *u8, cap: i64, off: i64) -> i64 { 105 var cur: i64 = off 106 cur = sm_put(out, cap, cur, 107 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n", 38) 108 if cur < 0 { return cur } 109 cur = sm_put(out, cap, cur, 110 "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n", 62) 111 return cur 112} 113 114// Emit one <url>. Pass -1 for lastmod_unix to omit it. 115// Pass SM_FREQ_NONE for freq to omit changefreq. 116// Pass -1 for priority_tenths to omit. Priority is an integer 117// 0..10 (tenths of a unit); written as 0.N. 118func sitemap_url(out: *u8, cap: i64, off: i64, 119 loc: *u8, loc_len: i64, 120 lastmod_unix: i64, 121 freq: i64, 122 priority_tenths: i64) -> i64 { 123 var cur: i64 = off 124 cur = sm_put(out, cap, cur, "<url>\n<loc>", 11) 125 if cur < 0 { return cur } 126 cur = sm_put_escaped(out, cap, cur, loc, loc_len) 127 if cur < 0 { return cur } 128 cur = sm_put(out, cap, cur, "</loc>\n", 7) 129 if cur < 0 { return cur } 130 131 if lastmod_unix >= 0 { 132 cur = sm_put(out, cap, cur, "<lastmod>", 9) 133 if cur < 0 { return cur } 134 cur = sm_put_date(out, cap, cur, lastmod_unix) 135 if cur < 0 { return cur } 136 cur = sm_put(out, cap, cur, "</lastmod>\n", 11) 137 if cur < 0 { return cur } 138 } 139 140 cur = sm_put_freq(out, cap, cur, freq) 141 if cur < 0 { return cur } 142 143 if priority_tenths >= 0 { 144 if priority_tenths <= 10 { 145 cur = sm_put(out, cap, cur, "<priority>", 10) 146 if cur < 0 { return cur } 147 if priority_tenths == 10 { 148 cur = sm_put(out, cap, cur, "1.0", 3) 149 } else { 150 if cur + 3 > cap { return SM_ERR_SHORT } 151 out[cur] = 0x30 152 out[cur + 1] = 0x2E 153 out[cur + 2] = 0x30 + priority_tenths 154 cur = cur + 3 155 } 156 if cur < 0 { return cur } 157 cur = sm_put(out, cap, cur, "</priority>\n", 12) 158 if cur < 0 { return cur } 159 } 160 } 161 162 cur = sm_put(out, cap, cur, "</url>\n", 7) 163 return cur 164} 165 166func sitemap_end(out: *u8, cap: i64, off: i64) -> i64 { 167 return sm_put(out, cap, off, "</urlset>\n", 10) 168} 169 170// Compile-only smoke. 171func main() -> i64 { 172 let out: *u8 = sys_mmap(4096) 173 var off: i64 = 0 174 off = sitemap_begin(out, 4096, off) 175 if off < 0 { return 1 } 176 177 off = sitemap_url(out, 4096, off, 178 "https://nishifamily.com/", 24, 179 1777000000, SM_FREQ_WEEKLY, 10) 180 if off < 0 { return 2 } 181 182 off = sitemap_url(out, 4096, off, 183 "https://nishifamily.com/blog/1", 30, 184 1777000000, SM_FREQ_MONTHLY, 8) 185 if off < 0 { return 3 } 186 187 off = sitemap_end(out, 4096, off) 188 if off < 0 { return 4 } 189 190 if out[0] != 0x3C { return 5 } 191 if out[1] != 0x3F { return 6 } 192 return 0 193}