1use rupg_analyze::{XmlExpr, XmlOp};
4use rupg_common::{Error, Result, SqlState};
5use rupg_pgcatalog::builtin;
6use rupg_types::{
7 DateFormat, DateOrder, DateStyle, Value, XmlStandalone, date_out, escape_xml, oid,
8 timestamp_out, timestamptz_out, xml_base64, xml_binhex, xml_comment, xml_concat, xml_element,
9 xml_in, xml_pi, xml_root, xml_text,
10};
11
12use crate::io::{array_of, base_type, output};
13use crate::reg::text_arg;
14use crate::{Call, Kernel, Session, bad_value, type_error};
15
16pub(crate) fn by_src(src: &str) -> Option<Kernel> {
18 Some(match src {
19 "xmlcomment" => xmlcomment,
20 "xmltext" => xmltext,
21 "texttoxml" => texttoxml,
22 "xmltotext" => xmltotext,
23 "xmlconcat2" => xmlconcat2,
24 "xmlvalidate" => xmlvalidate,
25 "xml_is_well_formed" => xml_is_well_formed,
26 "xml_is_well_formed_document" => xml_is_well_formed_document,
27 "xml_is_well_formed_content" => xml_is_well_formed_content,
28 _ => return None,
29 })
30}
31
32fn document_option(session: &dyn Session) -> bool {
34 session.setting("xmloption").is_some_and(|v| v.eq_ignore_ascii_case("document"))
35}
36
37fn parse(text: &str, document: bool, session: &dyn Session) -> Result<()> {
39 xml_in(text.as_bytes(), document, &mut |warning| {
40 session.warn(Error::new(SqlState::WARNING, warning));
41 })
42 .map_err(type_error)
43}
44
45fn well_formed(text: &str, document: bool, session: &dyn Session) -> bool {
47 parse(text, document, session).is_ok()
48}
49
50fn xmlcomment(_: &Call<'_>, args: &[Value]) -> Result<Value> {
51 Ok(Value::text(xml_comment(text_arg(args)?).map_err(type_error)?))
52}
53
54fn xmltext(_: &Call<'_>, args: &[Value]) -> Result<Value> {
55 Ok(Value::text(xml_text(text_arg(args)?)))
56}
57
58fn texttoxml(call: &Call<'_>, args: &[Value]) -> Result<Value> {
60 let text = text_arg(args)?;
61 parse(text, document_option(call.session), call.session)?;
62 Ok(Value::text(text))
63}
64
65fn xmltotext(_: &Call<'_>, args: &[Value]) -> Result<Value> {
67 Ok(Value::text(text_arg(args)?))
68}
69
70fn xmlconcat2(_: &Call<'_>, args: &[Value]) -> Result<Value> {
72 match args {
73 [Value::Null, Value::Null] => Ok(Value::Null),
74 [Value::Null, v] | [v, Value::Null] => Ok(v.clone()),
75 [a, b] => {
76 let (a, b) = (a.as_str().ok_or_else(bad_value)?, b.as_str().ok_or_else(bad_value)?);
77 Ok(Value::text(xml_concat(&[a, b])))
78 }
79 _ => Err(bad_value()),
80 }
81}
82
83fn xmlvalidate(_: &Call<'_>, _: &[Value]) -> Result<Value> {
84 Err(Error::new(SqlState::FEATURE_NOT_SUPPORTED, "xmlvalidate is not implemented"))
85}
86
87fn xml_is_well_formed(call: &Call<'_>, args: &[Value]) -> Result<Value> {
88 let document = document_option(call.session);
89 Ok(Value::Bool(well_formed(text_arg(args)?, document, call.session)))
90}
91
92fn xml_is_well_formed_document(call: &Call<'_>, args: &[Value]) -> Result<Value> {
93 Ok(Value::Bool(well_formed(text_arg(args)?, true, call.session)))
94}
95
96fn xml_is_well_formed_content(call: &Call<'_>, args: &[Value]) -> Result<Value> {
97 Ok(Value::Bool(well_formed(text_arg(args)?, false, call.session)))
98}
99
100fn infinite(what: &str) -> Error {
102 Error::new(SqlState::DATETIME_VALUE_OUT_OF_RANGE, format!("{what} out of range"))
103 .with_detail(format!("XML does not support infinite {what} values."))
104}
105
106pub fn xml_value(ty: u32, value: &Value, escape: bool, session: &dyn Session) -> Result<String> {
112 let ty = base_type(ty);
113 if let Value::Array(array) = value {
114 let element = array_of(ty)
115 .map(|(e, _)| e)
116 .or_else(|| builtin::type_by_oid(ty).map(|row| row.elem))
117 .unwrap_or(oid::UNKNOWN);
118 let mut out = String::new();
119 for v in array.values.iter().flatten() {
120 out.push_str("<element>");
121 out.push_str(&xml_value(element, v, true, session)?);
122 out.push_str("</element>");
123 }
124 return Ok(out);
125 }
126 let format = DateFormat { style: DateStyle::Xsd, order: DateOrder::Mdy };
127 let mut buf = Vec::new();
128 match (ty, value) {
129 (oid::BOOL, Value::Bool(b)) => return Ok(if *b { "true" } else { "false" }.to_string()),
130 (oid::DATE, Value::Date(v)) => {
131 date_out(*v, format, &mut buf);
132 if buf.ends_with(b"infinity") {
133 return Err(infinite("date"));
134 }
135 }
136 (oid::TIMESTAMP, Value::Timestamp(v)) => {
137 timestamp_out(*v, format, &mut buf).map_err(type_error)?;
138 if buf.ends_with(b"infinity") {
139 return Err(infinite("timestamp"));
140 }
141 }
142 (oid::TIMESTAMPTZ, Value::TimestampTz(v)) => {
143 timestamptz_out(*v, format, &*session.zone()?, &mut buf).map_err(type_error)?;
144 if buf.ends_with(b"infinity") {
145 return Err(infinite("timestamp"));
146 }
147 }
148 (oid::BYTEA, Value::Bytea(bytes)) => {
149 let hex = session.setting("xmlbinary").is_some_and(|v| v.eq_ignore_ascii_case("hex"));
150 return Ok(if hex { xml_binhex(bytes) } else { xml_base64(bytes) });
151 }
152 _ => {
153 output(ty, value, session, &mut buf)?;
154 let text = String::from_utf8(buf).map_err(|_| bad_value())?;
155 return Ok(if ty == oid::XML || !escape { text } else { escape_xml(&text) });
156 }
157 }
158 String::from_utf8(buf).map_err(|_| bad_value())
159}
160
161fn text_of(value: &Value) -> Result<&str> {
163 value.as_str().ok_or_else(bad_value)
164}
165
166pub fn xml_expr(x: &XmlExpr, values: &[Value], session: &dyn Session) -> Result<Value> {
172 let (named, args) = values.split_at(x.named.len().min(values.len()));
173 let arg = |i: usize| args.get(i).ok_or_else(bad_value);
174 let name = x.name.as_deref().unwrap_or_default();
175 Ok(match x.op {
176 XmlOp::Concat => {
177 let texts: Vec<&str> =
178 args.iter().filter(|v| !v.is_null()).map(text_of).collect::<Result<_>>()?;
179 if texts.is_empty() { Value::Null } else { Value::text(xml_concat(&texts)) }
180 }
181 XmlOp::Element => {
182 let mut attributes = Vec::new();
183 for ((attr, e), v) in x.named.iter().zip(named) {
184 if !v.is_null() {
185 attributes.push((attr.as_str(), xml_value(e.ty, v, false, session)?));
186 }
187 }
188 let mut content = Vec::new();
189 for (e, v) in x.args.iter().zip(args) {
190 if !v.is_null() {
191 content.push(xml_value(e.ty, v, true, session)?);
192 }
193 }
194 let attributes: Vec<(&str, &str)> =
195 attributes.iter().map(|(n, v)| (*n, v.as_str())).collect();
196 Value::text(xml_element(name, &attributes, &content))
197 }
198 XmlOp::Forest => {
199 let mut out = String::new();
200 let mut any = false;
201 for ((tag, e), v) in x.named.iter().zip(named) {
202 if !v.is_null() {
203 any = true;
204 out.push_str(&format!("<{tag}>{}</{tag}>", xml_value(e.ty, v, true, session)?));
205 }
206 }
207 if any { Value::text(out) } else { Value::Null }
208 }
209 XmlOp::Parse => match arg(0)? {
210 Value::Null => Value::Null,
211 v => {
212 let text = text_of(v)?;
213 parse(text, x.document, session)?;
214 Value::text(text)
215 }
216 },
217 XmlOp::Pi => {
218 let (text, null) = match args.first() {
219 Some(Value::Null) => (None, true),
220 Some(v) => (Some(text_of(v)?), false),
221 None => (None, false),
222 };
223 xml_pi(name, text, null).map_err(type_error)?.map_or(Value::Null, Value::text)
224 }
225 XmlOp::Root => match arg(0)? {
226 Value::Null => Value::Null,
227 data => {
228 let version = match arg(1)? {
229 Value::Null => None,
230 v => Some(text_of(v)?),
231 };
232 let standalone = match arg(2)? {
233 Value::Int4(0) => XmlStandalone::Yes,
234 Value::Int4(1) => XmlStandalone::No,
235 Value::Int4(2) => XmlStandalone::NoValue,
236 _ => XmlStandalone::Omitted,
237 };
238 Value::text(xml_root(text_of(data)?, version, standalone))
239 }
240 },
241 XmlOp::Serialize => match arg(0)? {
242 Value::Null => Value::Null,
243 v => {
244 let text = text_of(v)?;
245 if x.indent {
246 return Err(crate::not_yet("XMLSERIALIZE with INDENT"));
247 }
248 if x.document && xml_in(text.as_bytes(), true, &mut |_| {}).is_err() {
249 return Err(Error::new(SqlState::NOT_AN_XML_DOCUMENT, "not an XML document"));
250 }
251 Value::text(text)
252 }
253 },
254 XmlOp::Document => match arg(0)? {
255 Value::Null => Value::Null,
256 v => Value::Bool(well_formed(text_of(v)?, true, session)),
257 },
258 })
259}