Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[refactor] SapTable 내용 파싱 개선 #31

Merged
merged 7 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/application/course_grades/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use crate::{
application::client::body::Body,
element::{
action::Button,
complex::sap_table::{cell::SapTableCellWrapper, property::SapTableCellType, SapTable},
complex::sap_table::{
cell::SapTableCellWrapper, property::SapTableCellType, row::SapTableRow, SapTable,
},
layout::PopupWindow,
selection::ComboBox,
text::InputField,
Expand Down Expand Up @@ -159,7 +161,7 @@ impl<'a> CourseGrades {
}
}

fn row_to_string(row: &Vec<SapTableCellWrapper>) -> Option<Vec<String>> {
fn row_to_string(row: &SapTableRow) -> Option<Vec<String>> {
if row.len() == 0 || row[0].is_empty_row() {
return None;
};
Expand Down Expand Up @@ -277,7 +279,6 @@ impl<'a> CourseGrades {
})?;
let ret = table
.iter()
.skip(1)
.filter_map(Self::row_to_string)
.filter_map(|values| {
Some(SemesterGrade::new(
Expand Down Expand Up @@ -311,12 +312,12 @@ impl<'a> CourseGrades {
.next()
.ok_or(BodyError::NoSuchElement("Table in popup".to_string()))?;
let table_elem: SapTable<'_> = ElementWrapper::dyn_elem(table_ref)?.try_into()?;
let zip = (|| {
let mut iter = table_elem.table()?.iter();
let head_str = CourseGrades::row_to_string(iter.next()?)?;
let row_str = CourseGrades::row_to_string(iter.next()?)?;
Some(head_str.into_iter().zip(row_str.into_iter()))
})()
let zip = (|| Some(table_elem.table()?.zip_header().next()?))()
.and_then(|(header, row)| {
let header = CourseGrades::row_to_string(header)?;
let row = CourseGrades::row_to_string(row)?;
Some(header.into_iter().zip(row.into_iter()))
})
.ok_or(ElementError::InvalidContent {
element: table_elem.id().to_string(),
content: "header and first row".to_string(),
Expand Down Expand Up @@ -389,8 +390,7 @@ impl<'a> CourseGrades {
element: grade_table_elem.id().to_string(),
content: "Table body".to_string(),
})?
.iter()
.skip(1);
.iter();
iter.map(|row| {
let btn_cell = &row[10];
let btn_id = if let Some(ElementWrapper::Button(btn)) = btn_cell.content() {
Expand Down
9 changes: 3 additions & 6 deletions src/application/course_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ use crate::{
define_elements,
model::SemesterType,
webdynpro::{
element::{
action::Button, complex::SapTable, layout::TabStrip,
selection::ComboBox,
},
element::{action::Button, complex::SapTable, layout::TabStrip, selection::ComboBox},
error::WebDynproError,
},
};
Expand Down Expand Up @@ -168,9 +165,9 @@ mod test {
app.load_edu().await.unwrap();
let table = app.read_edu_raw().unwrap();
if let Some(table) = table.table() {
for row in table {
for row in table.with_header() {
print!("row: ");
for col in row {
for col in row.iter() {
match col {
SapTableCellWrapper::Header(cell) => {
let content = cell.content();
Expand Down
78 changes: 78 additions & 0 deletions src/webdynpro/element/complex/sap_table/body.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::{iter, ops::Deref};

use scraper::ElementRef;

use crate::webdynpro::{element::ElementDef, error::ElementError};

use super::{
property::SapTableRowType,
row::SapTableRow,
SapTable,
};

#[derive(custom_debug_derive::Debug)]
#[allow(unused)]
pub struct SapTableBody<'a> {
table_def: ElementDef<'a, SapTable<'a>>,
#[debug(skip)]
elem_ref: ElementRef<'a>,
header: SapTableRow<'a>,
rows: Vec<SapTableRow<'a>>,
}

impl<'a> SapTableBody<'a> {
pub(super) fn new(
table_def: ElementDef<'a, SapTable<'a>>,
elem_ref: ElementRef<'a>,
) -> Result<SapTableBody<'a>, ElementError> {
let rows_iter = elem_ref
.children()
.filter_map(|node| scraper::ElementRef::wrap(node))
.filter_map(|row_ref| SapTableRow::new(table_def.clone(), row_ref).ok());
let mut header_iter = rows_iter
.clone()
.filter(|row| matches!(row.row_type(), SapTableRowType::Header));
let Some(header) = header_iter.next() else {
return Err(ElementError::NoSuchContent { element: table_def.id().to_owned(), content: "Header of table".to_owned() });
};
if header_iter.next().is_some() {
return Err(ElementError::InvalidContent {
element: table_def.id().to_owned(),
content: "Multiple header in table".to_owned(),
});
}
let rows = rows_iter.skip(1).collect::<Vec<SapTableRow<'a>>>();
Ok(SapTableBody {
table_def,
elem_ref,
header,
rows,
})
}

pub fn zip_header(&'a self) -> impl Iterator<Item = (&SapTableRow, &SapTableRow)> {
let header_iter = iter::repeat(self.header());
header_iter
.zip(self.rows.iter())
}

pub fn with_header(&'a self) -> impl Iterator<Item = &SapTableRow> {
[self.header()].into_iter().chain(self.rows.iter())
}

pub fn table_def(&self) -> ElementDef<'a, SapTable<'a>> {
self.table_def.clone()
}

pub fn header(&self) -> &SapTableRow<'a> {
&self.header
}
}

impl<'a> Deref for SapTableBody<'a> {
type Target = Vec<SapTableRow<'a>>;

fn deref(&self) -> &Self::Target {
&self.rows
}
}
59 changes: 58 additions & 1 deletion src/webdynpro/element/complex/sap_table/cell/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::webdynpro::element::ElementWrapper;
use crate::webdynpro::element::{ElementDef, ElementWrapper, SubElement, SubElementDef};

#[derive(Debug)]
pub enum SapTableCellWrapper<'a> {
Expand All @@ -10,6 +10,61 @@ pub enum SapTableCellWrapper<'a> {
}

impl<'a> SapTableCellWrapper<'a> {
pub fn dyn_cell(
table_def: ElementDef<'a, SapTable<'a>>,
elem_ref: scraper::ElementRef<'a>,
) -> Option<SapTableCellWrapper<'a>> {
let subct_value = elem_ref.value();
match subct_value.attr("subct") {
Some(SapTableNormalCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableNormalCell>::new_dynamic(
table_def,
subct_value.id()?.to_owned(),
)
.from_elem(elem_ref)
.ok()?
.wrap(),
),
Some(SapTableHeaderCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableHeaderCell>::new_dynamic(
table_def,
subct_value.id()?.to_owned(),
)
.from_elem(elem_ref)
.ok()?
.wrap(),
),
Some(SapTableHierarchicalCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableHierarchicalCell>::new_dynamic(
table_def,
subct_value.id()?.to_owned(),
)
.from_elem(elem_ref)
.ok()?
.wrap(),
),
Some(SapTableMatrixCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableMatrixCell>::new_dynamic(
table_def,
subct_value.id()?.to_owned(),
)
.from_elem(elem_ref)
.ok()?
.wrap(),
),
Some(SapTableSelectionCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableSelectionCell>::new_dynamic(
table_def,
subct_value.id()?.to_owned(),
)
.from_elem(elem_ref)
.ok()?
.wrap(),
),
_ => None,
}
}

pub fn content(&self) -> Option<&ElementWrapper<'a>> {
match self {
SapTableCellWrapper::Normal(elem) => elem.content(),
Expand Down Expand Up @@ -37,3 +92,5 @@ pub use self::hierarchical_cell::{SapTableHierarchicalCell, SapTableHierarchical
pub use self::matrix_cell::{SapTableMatrixCell, SapTableMatrixCellLSData};
pub use self::normal_cell::{SapTableNormalCell, SapTableNormalCellLSData};
pub use self::selection_cell::{SapTableSelectionCell, SapTableSelectionCellLSData};

use super::SapTable;
93 changes: 12 additions & 81 deletions src/webdynpro/element/complex/sap_table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,12 @@ use std::{borrow::Cow, cell::OnceCell, collections::HashMap};
use scraper::Selector;

use crate::webdynpro::{
element::{define_element_interactable, ElementDef, Interactable, SubElement, SubElementDef},
element::{define_element_interactable, ElementDef, Interactable},
error::{BodyError, ElementError, WebDynproError},
event::Event,
};

use self::{
cell::{
SapTableCellWrapper, SapTableHeaderCell, SapTableHierarchicalCell, SapTableMatrixCell,
SapTableNormalCell, SapTableSelectionCell,
},
property::AccessType,
};

/// 테이블 내부 데이터
pub type SapTableBody<'a> = Vec<Vec<SapTableCellWrapper<'a>>>;
use self::{body::SapTableBody, property::AccessType};

define_element_interactable! {
#[doc = "테이블"]
Expand Down Expand Up @@ -57,86 +48,24 @@ impl<'a> SapTable<'a> {
}
};
let element = self.element_ref;
let elem_value = element.value();
let tbody_selector = Selector::parse(
format!(
r#"[id="{}-contentTBody"]"#,
elem_value.id().ok_or(ElementError::NoSuchData {
element.value().id().ok_or(ElementError::NoSuchData {
element: self.id.clone().into_owned(),
field: "id".to_string()
})?
)
.as_str(),
)
.or(Err(BodyError::InvalidSelector))?;
let tbody = element
.select(&tbody_selector)
.next()
.ok_or(ElementError::NoSuchContent {
element: self.id.clone().into_owned(),
content: "Table content".to_string(),
})?;
Ok(tbody
.children()
.filter_map(|node| scraper::ElementRef::wrap(node))
.map(|row_ref| -> Vec<SapTableCellWrapper<'a>> {
let subct_selector = Selector::parse("[subct]").unwrap();
let subcts = row_ref.select(&subct_selector);
subcts
.filter_map(|subct_ref| -> Option<SapTableCellWrapper<'a>> {
let subct_value = subct_ref.value();
match subct_value.attr("subct") {
Some(SapTableNormalCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableNormalCell>::new_dynamic(
def.clone(),
subct_value.id()?.to_owned(),
)
.from_elem(subct_ref)
.ok()?
.wrap(),
),
Some(SapTableHeaderCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableHeaderCell>::new_dynamic(
def.clone(),
subct_value.id()?.to_owned(),
)
.from_elem(subct_ref)
.ok()?
.wrap(),
),
Some(SapTableHierarchicalCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableHierarchicalCell>::new_dynamic(
def.clone(),
subct_value.id()?.to_owned(),
)
.from_elem(subct_ref)
.ok()?
.wrap(),
),
Some(SapTableMatrixCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableMatrixCell>::new_dynamic(
def.clone(),
subct_value.id()?.to_owned(),
)
.from_elem(subct_ref)
.ok()?
.wrap(),
),
Some(SapTableSelectionCell::SUBCONTROL_ID) => Some(
SubElementDef::<_, SapTableSelectionCell>::new_dynamic(
def.clone(),
subct_value.id()?.to_owned(),
)
.from_elem(subct_ref)
.ok()?
.wrap(),
),
_ => None,
}
})
.collect::<Vec<SapTableCellWrapper<'a>>>()
})
.collect())
let Some(tbody) = element.select(&tbody_selector).next() else {
return Err(ElementError::NoSuchContent {
element: self.id.clone().into_owned(),
content: "Table body".to_string(),
})?;
};
Ok(SapTableBody::new(def, tbody)?)
}

/// 테이블의 행을 선택하는 이벤트를 반환합니다.
Expand Down Expand Up @@ -184,5 +113,7 @@ impl<'a> SapTable<'a> {
}
}

pub mod body;
pub mod cell;
pub mod property;
pub mod row;
23 changes: 23 additions & 0 deletions src/webdynpro/element/complex/sap_table/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,36 @@ pub enum SapTableHierarchicalCellStatus {
Icon,
}

#[derive(Deserialize, Debug, Default, Clone, Copy)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SapTableSelectionState {
NotSelectable,
NotSelected,
Selected,
PrimarySelected,
#[default]
None,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "UPPERCASE")]
pub enum SapTableRowSelectionMassState {
None,
Indeterminate,
All,
}
#[derive(Deserialize, Debug, Default, Clone, Copy)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SapTableRowType {
#[default]
Unspecified,
Standard,
Header,
Filter,
TopFixed,
BottomFixed,
Pivot,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
Expand Down
Loading