1use crate::resolve::ResolvedMeta;
4use serde_json::Value;
5
6pub fn render_html(meta: &ResolvedMeta) -> String {
7 let mut out = String::new();
8 if let Some(ref t) = meta.title {
9 out.push_str(&format!("<title>{}</title>\n", escape(t)));
10 }
11 if let Some(ref d) = meta.description {
12 out.push_str(&format!(
13 "<meta name=\"description\" content=\"{}\">\n",
14 escape(d)
15 ));
16 }
17 let robots = if meta.noindex {
18 "noindex, nofollow"
19 } else {
20 "index, follow"
21 };
22 out.push_str(&format!(
23 "<meta name=\"robots\" content=\"{robots}\">\n"
24 ));
25 if let Some(ref c) = meta.canonical {
26 out.push_str(&format!(
27 "<link rel=\"canonical\" href=\"{}\">\n",
28 escape(c)
29 ));
30 }
31 for (lang, href) in &meta.hreflang {
32 out.push_str(&format!(
33 "<link rel=\"alternate\" hreflang=\"{}\" href=\"{}\">\n",
34 escape(lang),
35 escape(href)
36 ));
37 }
38
39 if let Some(ref t) = meta.title {
41 out.push_str(&format!(
42 "<meta property=\"og:title\" content=\"{}\">\n",
43 escape(t)
44 ));
45 }
46 if let Some(ref d) = meta.description {
47 out.push_str(&format!(
48 "<meta property=\"og:description\" content=\"{}\">\n",
49 escape(d)
50 ));
51 }
52 out.push_str(&format!(
53 "<meta property=\"og:type\" content=\"{}\">\n",
54 escape(&meta.og_type)
55 ));
56 if let Some(ref c) = meta.canonical {
57 out.push_str(&format!(
58 "<meta property=\"og:url\" content=\"{}\">\n",
59 escape(c)
60 ));
61 }
62 if let Some(ref img) = meta.image {
63 out.push_str(&format!(
64 "<meta property=\"og:image\" content=\"{}\">\n",
65 escape(img)
66 ));
67 }
68 if let Some(ref published) = meta.published {
69 out.push_str(&format!(
70 "<meta property=\"article:published_time\" content=\"{}\">\n",
71 published.to_rfc3339()
72 ));
73 }
74 if let Some(ref sn) = meta.site_name {
75 out.push_str(&format!(
76 "<meta property=\"og:site_name\" content=\"{}\">\n",
77 escape(sn)
78 ));
79 }
80 if let Some(ref loc) = meta.og_locale {
81 out.push_str(&format!(
82 "<meta property=\"og:locale\" content=\"{}\">\n",
83 escape(loc)
84 ));
85 }
86 for alt in &meta.og_locale_alternate {
87 out.push_str(&format!(
88 "<meta property=\"og:locale:alternate\" content=\"{}\">\n",
89 escape(alt)
90 ));
91 }
92
93 let card = if meta.image.is_some() {
95 "summary_large_image"
96 } else {
97 "summary"
98 };
99 out.push_str(&format!(
100 "<meta name=\"twitter:card\" content=\"{card}\">\n"
101 ));
102 if let Some(ref site) = meta.twitter_site {
103 out.push_str(&format!(
104 "<meta name=\"twitter:site\" content=\"{}\">\n",
105 escape(site)
106 ));
107 }
108 if let Some(ref t) = meta.title {
109 out.push_str(&format!(
110 "<meta name=\"twitter:title\" content=\"{}\">\n",
111 escape(t)
112 ));
113 }
114 if let Some(ref d) = meta.description {
115 out.push_str(&format!(
116 "<meta name=\"twitter:description\" content=\"{}\">\n",
117 escape(d)
118 ));
119 }
120 if let Some(ref img) = meta.image {
121 out.push_str(&format!(
122 "<meta name=\"twitter:image\" content=\"{}\">\n",
123 escape(img)
124 ));
125 }
126
127 for block in &meta.jsonld {
128 let with_ctx = ensure_context(block.clone());
129 if let Ok(s) = serde_json::to_string(&with_ctx) {
130 out.push_str("<script type=\"application/ld+json\">");
131 out.push_str(&s);
132 out.push_str("</script>\n");
133 }
134 }
135
136 out
137}
138
139fn ensure_context(mut v: Value) -> Value {
140 if let Value::Object(ref mut map) = v {
141 map.entry("@context".to_string())
142 .or_insert_with(|| Value::String("https://schema.org".into()));
143 }
144 v
145}
146
147fn escape(s: &str) -> String {
148 s.replace('&', "&")
149 .replace('<', "<")
150 .replace('>', ">")
151 .replace('"', """)
152}