xso_proc/field/
attribute.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! This module concerns the processing of attributes.
//!
//! In particular, it provides the `#[xml(attribute)]` implementation.

use quote::{quote, ToTokens};
use syn::*;

use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, NameRef, NamespaceRef};
use crate::scope::{AsItemsScope, FromEventsScope};
use crate::types::{
    as_optional_xml_text_fn, default_fn, from_xml_text_fn, text_codec_decode_fn,
    text_codec_encode_fn,
};

use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};

/// The field maps to an attribute.
pub(super) struct AttributeField {
    /// The optional XML namespace of the attribute.
    pub(super) xml_namespace: Option<NamespaceRef>,

    /// The XML name of the attribute.
    pub(super) xml_name: NameRef,

    /// Flag indicating whether the value should be defaulted if the
    /// attribute is absent.
    pub(super) default_: Flag,

    /// Optional codec to use.
    pub(super) codec: Option<Expr>,
}

impl Field for AttributeField {
    fn make_builder_part(
        &self,
        scope: &FromEventsScope,
        container_name: &ParentRef,
        member: &Member,
        ty: &Type,
    ) -> Result<FieldBuilderPart> {
        let FromEventsScope { ref attrs, .. } = scope;
        let ty = ty.clone();
        let xml_namespace = &self.xml_namespace;
        let xml_name = &self.xml_name;

        let missing_msg = error_message::on_missing_attribute(container_name, member);

        let xml_namespace = match xml_namespace {
            Some(v) => v.to_token_stream(),
            None => quote! {
                ::xso::exports::rxml::Namespace::none()
            },
        };

        let finalize = match self.codec {
            Some(ref codec) => {
                let decode = text_codec_decode_fn(ty.clone());
                quote! {
                    |value| #decode(&#codec, value)
                }
            }
            None => {
                let from_xml_text = from_xml_text_fn(ty.clone());
                quote! { #from_xml_text }
            }
        };

        let on_absent = match self.default_ {
            Flag::Absent => quote! {
                return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
            },
            Flag::Present(_) => {
                let default_ = default_fn(ty.clone());
                quote! {
                    #default_()
                }
            }
        };

        Ok(FieldBuilderPart::Init {
            value: FieldTempInit {
                init: quote! {
                    match #attrs.remove(#xml_namespace, #xml_name).map(#finalize).transpose()? {
                        ::core::option::Option::Some(v) => v,
                        ::core::option::Option::None => #on_absent,
                    }
                },
                ty: ty.clone(),
            },
        })
    }

    fn make_iterator_part(
        &self,
        _scope: &AsItemsScope,
        _container_name: &ParentRef,
        bound_name: &Ident,
        _member: &Member,
        ty: &Type,
    ) -> Result<FieldIteratorPart> {
        let xml_namespace = match self.xml_namespace {
            Some(ref v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
            None => quote! {
                ::xso::exports::rxml::Namespace::NONE
            },
        };
        let xml_name = &self.xml_name;

        let generator = match self.codec {
            Some(ref codec) => {
                let encode = text_codec_encode_fn(ty.clone());
                quote! { #encode(&#codec, #bound_name)? }
            }
            None => {
                let as_optional_xml_text = as_optional_xml_text_fn(ty.clone());
                quote! { #as_optional_xml_text(#bound_name)? }
            }
        };

        Ok(FieldIteratorPart::Header {
            generator: quote! {
                #generator.map(|#bound_name| ::xso::Item::Attribute(
                    #xml_namespace,
                    ::std::borrow::Cow::Borrowed(#xml_name),
                    #bound_name,
                ));
            },
        })
    }
}