aboutsummaryrefslogblamecommitdiffstats
path: root/src/writer.rs
blob: dc5b48aa3a4cc6deca7904643a58cf16fe57392a (plain) (tree)
1
2
3
4
5
6
7
8
9
                                              
 
                                     
                  
                                           

            
                                                                        
                 
                                                                     
           

  
                                       
                      
             
                     
                                                               

 






                                               
     
 
 















                                                                            
                                                       




                                                   











                                                                                             











                                                                                  






                                                                      


























































































                                                                                                 






                                                                                        
































                                                                                                 



                                                   
                                              
                                   











                                                                                            
              
     
 
                                                     
                                               
                      

                                                                       











                                                                                                 



















                                                                                     
     







                                                             
                                      


               
                                                                          





                                        
                                      





                                        
                                      


               

















                                                           
use std::{collections::HashSet, str::FromStr};

use async_recursion::async_recursion;
use futures::Sink;
use tokio::io::{AsyncWrite, AsyncWriteExt};

use crate::{
    element::{escape_str, Content, Element, Name, NamespaceDeclaration},
    error::Error,
    xml::{self, composers::Composer, parsers_complete::Parser, ETag},
    Result,
};

// pub struct Writer<W, C = Composer> {
pub struct Writer<W> {
    inner: W,
    depth: Vec<Name>,
    namespace_declarations: Vec<HashSet<NamespaceDeclaration>>,
}

impl<W> Writer<W> {
    pub fn new(writer: W) -> Self {
        Self {
            inner: writer,
            depth: Vec::new(),
            namespace_declarations: Vec::new(),
        }
    }
}

impl<W: AsyncWrite + Unpin + Send> Writer<W> {
    #[async_recursion]
    pub async fn write_element(&mut self, element: &Element) -> Result<()> {
        if element.content.is_empty() {
            self.write_empty(element).await?;
        } else {
            self.write_start(element).await?;
            for content in &element.content {
                self.write_content(content).await?;
            }
            self.write_end().await?;
        }
        Ok(())
    }

    pub async fn write_empty(&mut self, element: &Element) -> Result<()> {
        let namespace_declarations_stack: Vec<_> = self
            .namespace_declarations
            .iter()
            .flatten()
            .chain(&element.namespace_declarations)
            .collect();

        let prefix;
        if let Some(namespace) = &element.name.namespace {
            let name_namespace_declaration = namespace_declarations_stack
                .iter()
                .rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
                .ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
            prefix = name_namespace_declaration.prefix.as_ref();
        } else {
            prefix = None
        }

        let name;
        if let Some(prefix) = &prefix {
            name = xml::QName::PrefixedName(xml::PrefixedName {
                prefix: xml::Prefix::parse_full(prefix)?,
                local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
            })
        } else {
            name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
                &element.name.local_name,
            )?)
        }

        let mut attributes = Vec::new();

        for namespace_declaration in &element.namespace_declarations {
            let ns_name = namespace_declaration
                .prefix
                .as_ref()
                .map(|prefix| -> Result<_> {
                    Ok(xml::NSAttName::PrefixedAttName(xml::PrefixedAttName(
                        xml::NCName::parse_full(&prefix)?,
                    )))
                })
                .unwrap_or(Ok(xml::NSAttName::DefaultAttName))?;
            let value = xml::AttValue::from(namespace_declaration.namespace.as_str());
            let xml_attribute = xml::Attribute::NamespaceDeclaration { ns_name, value };
            attributes.push(xml_attribute);
        }

        for (name, value) in &element.attributes {
            let prefix;
            if let Some(namespace) = &name.namespace {
                let name_namespace_declaration = namespace_declarations_stack
                    .iter()
                    .rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
                    .ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
                prefix = name_namespace_declaration.prefix.as_ref();
            } else {
                prefix = None
            }

            let att_name;
            if let Some(prefix) = &prefix {
                att_name = xml::QName::PrefixedName(xml::PrefixedName {
                    prefix: xml::Prefix::parse_full(prefix)?,
                    local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
                })
            } else {
                att_name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
                    &element.name.local_name,
                )?)
            }

            let value = xml::AttValue::from(value.as_str());

            let xml_attribute = xml::Attribute::Attribute {
                name: att_name,
                value,
            };
            attributes.push(xml_attribute);
        }

        let tag = xml::EmptyElemTag { name, attributes };

        tag.write(&mut self.inner).await?;

        Ok(())
    }

    pub async fn write_start(&mut self, element: &Element) -> Result<()> {
        let namespace_declarations_stack: Vec<_> = self
            .namespace_declarations
            .iter()
            .flatten()
            .chain(&element.namespace_declarations)
            .collect();

        let prefix;
        if let Some(namespace) = &element.name.namespace {
            let name_namespace_declaration = namespace_declarations_stack
                .iter()
                .rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
                .ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
            prefix = name_namespace_declaration.prefix.as_ref();
        } else {
            prefix = None
        }

        let name;
        if let Some(prefix) = &prefix {
            name = xml::QName::PrefixedName(xml::PrefixedName {
                prefix: xml::Prefix::parse_full(prefix)?,
                local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
            })
        } else {
            name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
                &element.name.local_name,
            )?)
        }

        let mut attributes = Vec::new();

        for namespace_declaration in &element.namespace_declarations {
            let ns_name = namespace_declaration
                .prefix
                .as_ref()
                .map(|prefix| -> Result<_> {
                    Ok(xml::NSAttName::PrefixedAttName(xml::PrefixedAttName(
                        xml::NCName::parse_full(&prefix)?,
                    )))
                })
                .unwrap_or(Ok(xml::NSAttName::DefaultAttName))?;
            let value = xml::AttValue::from(namespace_declaration.namespace.as_str());
            let xml_attribute = xml::Attribute::NamespaceDeclaration { ns_name, value };
            attributes.push(xml_attribute);
        }

        for (name, value) in &element.attributes {
            let prefix;
            if let Some(namespace) = &name.namespace {
                let name_namespace_declaration = namespace_declarations_stack
                    .iter()
                    .rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
                    .ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
                prefix = name_namespace_declaration.prefix.as_ref();
            } else {
                prefix = None
            }

            let att_name;
            if let Some(prefix) = &prefix {
                att_name = xml::QName::PrefixedName(xml::PrefixedName {
                    prefix: xml::Prefix::parse_full(prefix)?,
                    local_part: xml::LocalPart::parse_full(&element.name.local_name)?,
                })
            } else {
                att_name = xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
                    &element.name.local_name,
                )?)
            }

            let value = xml::AttValue::from(value.as_str());

            let xml_attribute = xml::Attribute::Attribute {
                name: att_name,
                value,
            };
            attributes.push(xml_attribute);
        }

        let s_tag = xml::STag { name, attributes };

        s_tag.write(&mut self.inner).await?;

        self.depth.push(element.name.clone());
        self.namespace_declarations
            .push(element.namespace_declarations.clone());
        Ok(())
    }

    pub async fn write_content(&mut self, content: &Content) -> Result<()> {
        match content {
            Content::Element(element) => self.write_element(element).await?,
            Content::Text(text) => self.inner.write_all(escape_str(text).as_bytes()).await?,
            // TODO: comments and PI
            Content::PI => {}
            Content::Comment(_) => {}
        }
        Ok(())
    }

    pub async fn write_end(&mut self) -> Result<()> {
        if let Some(name) = &self.depth.pop() {
            let e_tag;
            let namespace_declarations_stack: Vec<_> =
                self.namespace_declarations.iter().flatten().collect();

            let prefix;
            if let Some(namespace) = &name.namespace {
                let name_namespace_declaration = namespace_declarations_stack
                    .iter()
                    .rfind(|namespace_declaration| namespace_declaration.namespace == *namespace)
                    .ok_or(Error::UndeclaredNamespace(namespace.clone()))?;
                prefix = name_namespace_declaration.prefix.as_ref();
            } else {
                prefix = None
            }

            if let Some(prefix) = &prefix {
                e_tag = xml::ETag {
                    name: xml::QName::PrefixedName(xml::PrefixedName {
                        prefix: xml::Prefix::parse_full(prefix)?,
                        local_part: xml::LocalPart::parse_full(&name.local_name)?,
                    }),
                };
            } else {
                e_tag = xml::ETag {
                    name: xml::QName::UnprefixedName(xml::UnprefixedName::parse_full(
                        &name.local_name,
                    )?),
                };
            }
            e_tag.write(&mut self.inner).await?;
            self.namespace_declarations.pop();
            Ok(())
        } else {
            return Err(Error::NotInElement("".to_string()));
        }
    }
}

impl<W: AsyncWrite, E: Into<Element>> Sink<E> for Writer<W> {
    type Error = Error;

    fn poll_ready(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<()>> {
        todo!()
    }

    fn start_send(self: std::pin::Pin<&mut Self>, item: E) -> Result<()> {
        todo!()
    }

    fn poll_flush(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<()>> {
        todo!()
    }

    fn poll_close(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<()>> {
        todo!()
    }
}

#[cfg(test)]
mod test {
    use crate::{
        reader::{test::*, Reader},
        writer::Writer,
    };

    #[tokio::test]
    async fn test_element_write() {
        let mock = MockAsyncReader::new(TEST_DOC);
        let mut reader = Reader::new(mock);
        let element = reader.read_element().await.unwrap();
        let stdout = tokio::io::stdout();
        let mut writer = Writer::new(stdout);
        writer.write_element(&element).await.unwrap();
    }
}