use std::collections::HashMap;
use regex_lite::Regex;
use scraper::ElementRef;
use selection::CheckBox;
use self::{
action::{Button, Link},
complex::SapTable,
definition::ElementDefinition,
graphic::Image,
layout::{
ButtonRow, Container, FlowLayout, Form, GridLayout, PopupWindow, ScrollContainer,
Scrollbar, TabStrip, Tray, grid_layout::cell::GridLayoutCell,
tab_strip::item::TabStripItem,
},
selection::{
ComboBox,
list_box::{
ListBoxMultiple, ListBoxPopup, ListBoxPopupFiltered, ListBoxPopupJson,
ListBoxPopupJsonFiltered, ListBoxSingle,
item::{ListBoxActionItem, ListBoxItem},
},
},
system::{ClientInspector, Custom, LoadingPlaceholder},
text::{Caption, InputField, Label, TextView},
};
use super::{
error::{BodyError, ElementError, WebDynproError},
event::{Event, EventBuilder, ucf_parameters::UcfParameters},
};
pub mod sub;
pub mod definition;
pub mod parser;
pub mod action;
pub mod complex;
pub mod graphic;
pub mod layout;
pub mod selection;
pub mod system;
pub mod text;
pub mod unknown;
pub mod property;
pub mod registry;
pub type EventParameterMap = HashMap<String, (UcfParameters, HashMap<String, String>)>;
macro_rules! element_wrapper_impls {
[$( $enum:ident : $type: ty ),+ $(,)?] => {
#[allow(missing_docs, clippy::large_enum_variant)]
pub enum ElementWrapper<'a> {
$( $enum($type), )*
Unknown($crate::element::unknown::Unknown<'a>)
}
impl<'a> std::fmt::Debug for ElementWrapper<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$( ElementWrapper::$enum(elem) => {
f.debug_struct(stringify!($enum))
.field("id", &elem.id().to_string())
.finish()
},)+
ElementWrapper::Unknown(elem) => {
f.debug_struct("Unknown")
.field("ct", &elem.ct().to_owned())
.field("id", &elem.id().to_string())
.finish()
},
}
}
}
impl<'a> ElementWrapper<'a> {
pub fn from_def(wrapper: &'a ElementDefWrapper, parser: &'a $crate::element::parser::ElementParser) -> Result<ElementWrapper<'a>, WebDynproError> {
match wrapper {
$( ElementDefWrapper::$enum(def) => Ok(parser.element_from_def(def)?.wrap()), )*
ElementDefWrapper::Unknown(def) => Ok(parser.element_from_def(def)?.wrap()),
}
}
pub fn id(&self) -> &str {
match self {
$( ElementWrapper::$enum(element) => <$type as $crate::element::Element<'a>>::id(element), )*
ElementWrapper::Unknown(element) => <$crate::element::unknown::Unknown<'a> as $crate::element::Element<'a>>::id(element),
}
}
}
$(
impl<'a> std::convert::TryFrom<ElementWrapper<'a>> for $type {
type Error = $crate::error::BodyError;
fn try_from(wrapper: ElementWrapper<'a>) -> Result<$type, Self::Error> {
match wrapper {
ElementWrapper::$enum(res) => Ok(res),
_ => Err(Self::Error::InvalidElement)
}
}
}
)+
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub enum ElementDefWrapper<'a> {
$( $enum(<$type as $crate::element::Element<'a>>::Def), )*
Unknown(<$crate::element::unknown::Unknown<'a> as $crate::element::Element<'a>>::Def)
}
impl<'a> ElementDefWrapper<'a> {
pub fn from_ref(element: scraper::ElementRef<'a>) -> Result<ElementDefWrapper<'a>, WebDynproError> {
let value = element.value();
let id = value.id().ok_or(BodyError::NoSuchAttribute("id".to_owned()))?.to_owned();
#[allow(unreachable_patterns)]
match element.value().attr("ct") {
$( Some(<$type>::CONTROL_ID) => Ok(ElementDefWrapper::$enum(<$type as $crate::element::Element<'a>>::Def::new_dynamic(id))), )*
_ => Ok(ElementDefWrapper::Unknown(<$crate::element::unknown::Unknown<'a> as $crate::element::Element<'a>>::Def::new_dynamic(id)))
}
}
pub fn id(&self) -> &str {
match self {
$( ElementDefWrapper::$enum(element_def) => <$type as $crate::element::Element<'a>>::Def::id(element_def), )*
ElementDefWrapper::Unknown(element_def) => <$crate::element::unknown::Unknown<'a> as $crate::element::Element<'a>>::Def::id(element_def),
}
}
pub fn selector(&self) -> Result<scraper::Selector, WebDynproError> {
match self {
$( ElementDefWrapper::$enum(element_def) => <$type as $crate::element::Element<'a>>::Def::selector(element_def), )*
ElementDefWrapper::Unknown(element_def) => <$crate::element::unknown::Unknown<'a> as $crate::element::Element<'a>>::Def::selector(element_def),
}
}
}
};
}
element_wrapper_impls![
Button: Button<'a>,
ButtonRow: ButtonRow<'a>,
CheckBox: CheckBox<'a>,
ClientInspector: ClientInspector<'a>,
ComboBox: ComboBox<'a>,
Container: Container<'a>,
Custom: Custom,
FlowLayout: FlowLayout<'a>,
Form: Form<'a>,
GridLayout: GridLayout<'a>,
GridLayoutCell: GridLayoutCell<'a>,
Image: Image<'a>,
InputField: InputField<'a>,
Label: Label<'a>,
Link: Link<'a>,
ListBoxPopup: ListBoxPopup<'a>,
ListBoxPopupFiltered: ListBoxPopupFiltered<'a>,
ListBoxPopupJson: ListBoxPopupJson<'a>,
ListBoxPopupJsonFiltered: ListBoxPopupJsonFiltered<'a>,
ListBoxMultiple: ListBoxMultiple<'a>,
ListBoxSingle: ListBoxSingle<'a>,
ListBoxItem: ListBoxItem<'a>,
ListBoxActionItem: ListBoxActionItem<'a>,
LoadingPlaceholder: LoadingPlaceholder<'a>,
PopupWindow: PopupWindow<'a>,
TabStrip: TabStrip<'a>,
TabStripItem: TabStripItem<'a>,
Tray: Tray<'a>,
SapTable: SapTable<'a>,
Scrollbar: Scrollbar<'a>,
ScrollContainer: ScrollContainer<'a>,
TextView: TextView<'a>,
Caption: Caption<'a>,
];
impl<'a> ElementWrapper<'a> {
pub fn from_ref(
element: scraper::ElementRef<'a>,
) -> Result<ElementWrapper<'a>, WebDynproError> {
let value = element.value();
let id = value
.id()
.ok_or(BodyError::NoSuchAttribute("id".to_owned()))?
.to_owned();
if let Some(ct) = value.attr("ct")
&& let Some(factory) = registry::registry_map().get(ct)
{
return factory(id, element);
}
let def = unknown::UnknownDef::new_dynamic(id);
Ok(unknown::Unknown::from_ref(&def, element)?.wrap())
}
}
#[macro_export]
macro_rules! define_elements {
($(
$(#[$attr:meta])*
$v:vis $name:ident : $eltype:tt<$lt:lifetime> = $id:literal
;)+) => {
$(
$(#[$attr])*
$v const $name: <$eltype<$lt> as $crate::element::Element<$lt>>::Def = <$eltype<$lt> as $crate::element::Element<$lt>>::Def::new($id);
)*
};
($(
$(#[$attr:meta])*
$name:ident : $eltype:tt<$lt:lifetime> = $id:literal
;)+) => {
$(
$(#[$attr])*
const $name: <$eltype<$lt> as $crate::element::Element<$lt>>::Def = <$eltype<$lt> as $crate::element::Element<$lt>>::Def::new($id);
)*
}
}
pub use define_elements;
fn normalize_lsjson(lsjson: &str) -> String {
let quote_key = Regex::new(r"([{,])(\w+):").unwrap();
let quote_to_double = Regex::new(r"([^\\])'([\s\S]*?)'").unwrap();
let convert_escape_to_rust = Regex::new(r"\\x([a-f0-9]{2})").unwrap();
let quoted = quote_key.replace_all(lsjson, r#"$1"$2":"#).into_owned();
let double_quoted = quote_to_double
.replace_all("ed, r#"$1"$2""#)
.into_owned();
convert_escape_to_rust
.replace_all(&double_quoted, r"\u00$1")
.into_owned()
}
pub trait Element<'a>: Sized {
const CONTROL_ID: &'static str;
const ELEMENT_NAME: &'static str;
type ElementLSData;
type Def: ElementDefinition<'a>;
fn from_ref(
elem_def: &impl ElementDefinition<'a>,
element: ElementRef<'a>,
) -> Result<Self, WebDynproError>;
fn children(&self) -> Vec<ElementWrapper<'a>>;
fn lsdata(&self) -> &Self::ElementLSData;
fn id(&self) -> &str;
fn element_ref(&self) -> &ElementRef<'a>;
fn wrap(self) -> ElementWrapper<'a>;
}
pub trait Interactable<'a>: Element<'a> {
unsafe fn fire_event_unchecked(
event: String,
parameters: HashMap<String, String>,
ucf_params: UcfParameters,
custom_params: HashMap<String, String>,
) -> Event {
EventBuilder::default()
.control(Self::ELEMENT_NAME.to_owned())
.event(event)
.parameters(parameters)
.ucf_parameters(ucf_params)
.custom_parameters(custom_params)
.build()
.unwrap()
}
fn event_parameter(
&self,
event: &str,
) -> Result<&(UcfParameters, HashMap<String, String>), ElementError> {
if let Some(lsevents) = self.lsevents() {
lsevents.get(event).ok_or(ElementError::NoSuchEvent {
element: self.id().to_string(),
event: event.to_string(),
})
} else {
Err(ElementError::NoSuchEvent {
element: self.id().to_string(),
event: event.to_string(),
})
}
}
fn fire_event(
&self,
event: String,
parameters: HashMap<String, String>,
) -> Result<Event, WebDynproError> {
let (ucf_params, custom_params) = self.event_parameter(&event)?;
Ok(unsafe {
Self::fire_event_unchecked(
event,
parameters,
ucf_params.to_owned(),
custom_params.to_owned(),
)
})
}
fn lsevents(&self) -> Option<&EventParameterMap>;
}
impl ElementWrapper<'_> {
#[deprecated(
since = "0.11.3",
note = "Use `TryInto<String>` trait implementation instead."
)]
pub fn textise(&self) -> Result<String, WebDynproError> {
self.try_into()
}
}
impl TryFrom<&ElementWrapper<'_>> for String {
type Error = WebDynproError;
fn try_from(wrapper: &ElementWrapper<'_>) -> Result<Self, Self::Error> {
for reg in inventory::iter::<crate::element::registry::TextisableRegistration> {
if let Some(s) = (reg.to_string_fn)(wrapper) {
return Ok(s);
}
}
Err(ElementError::InvalidContent {
element: wrapper.id().to_string(),
content: format!(
"Element {:?} does not support text conversion (textise).",
wrapper
),
}
.into())
}
}
impl TryFrom<ElementWrapper<'_>> for String {
type Error = WebDynproError;
fn try_from(value: ElementWrapper<'_>) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
mod utils;