Skip to main content

screenplay_doc_parser_rs/
pdf_parser.rs

1//! This module is responsible for interpereting a (hopefully properlyformatted)
2//! PDF document into a usable, semantically-typed ScreenplayDocument structure.
3
4use core::num;
5use core::time;
6use std::collections::HashMap;
7use std::collections::HashSet;
8use std::hash::Hash;
9use std::ops::Not;
10use std::process::id;
11use std::sync::RwLockReadGuard;
12use std::thread::panicking;
13
14use uuid::Uuid;
15
16use crate::pdf_document;
17use crate::pdf_document::ElementIndentationsInches;
18use crate::pdf_document::ElementIndentationsPoints;
19use crate::screenplay_document::Character;
20use crate::screenplay_document::Environment;
21use crate::screenplay_document::EnvironmentStrings;
22use crate::screenplay_document::LocationID;
23use crate::screenplay_document::LocationNode;
24use crate::screenplay_document::PageNumber;
25use crate::screenplay_document::SPType;
26
27use crate::screenplay_document;
28use crate::screenplay_document::Scene;
29use crate::screenplay_document::SceneHeadingElement;
30use crate::screenplay_document::SceneID;
31use crate::screenplay_document::SceneNumber;
32use crate::screenplay_document::ScreenplayCoordinate;
33use crate::screenplay_document::TextElement;
34
35pub mod indentations_deducer;
36
37pub fn deduce_indentations(
38    pdfdoc: &pdf_document::PDFDocument,
39) -> Option<ElementIndentationsInches> {
40    unimplemented!();
41    let mut x_pos_vec: Vec<f64> = Vec::default();
42
43    let mut lines_count = 0;
44    for page in &pdfdoc.pages {
45        for ln in &page.lines {
46            if let Some(word) = ln.words.first() {
47                x_pos_vec.push(word.position.x);
48                lines_count += 1;
49            }
50        }
51    }
52
53    let mut x_freq_map: HashMap<i32, i32> = HashMap::new();
54
55    for entry in x_pos_vec {
56        let rounded = entry.round() as i32;
57        let new_ent = x_freq_map.entry(rounded).or_insert(0);
58        *new_ent += 1;
59    }
60
61    let mut x_freq_keys: Vec<i32> = x_freq_map.keys().cloned().collect();
62    x_freq_keys.sort();
63
64    for fk in &x_freq_keys {
65        let v = x_freq_map.get(&fk);
66        println!(
67            "INDENT_INCHES: {:10.2} FREQUENCY: {:6.2}%",
68            fk.clone() as f64 / 72.0,
69            {
70                if let Some(freq) = v {
71                    let fr = *freq;
72                    (fr as f64 / lines_count as f64) * 100.0
73                } else {
74                    0.0
75                }
76            }
77        )
78    }
79
80    None
81}
82
83fn _is_word_within_content_zone(
84    pdf_word: &pdf_document::Word,
85    element_indentaions_pts: &ElementIndentationsPoints,
86) -> bool {
87    todo!()
88}
89
90fn _check_non_content_type(
91    pdf_word: &pdf_document::Word,
92    new_line: &screenplay_document::Line,
93    element_indentaions_pts: &ElementIndentationsPoints,
94    time_of_day_strs: &screenplay_document::TimeOfDayCollection,
95    environment_strs: &screenplay_document::EnvironmentStrings,
96    r_marker: &String,
97) -> Option<SPType> {
98    todo!()
99}
100
101fn _get_type_for_word(
102    pdf_word: &pdf_document::Word,
103    new_line: &screenplay_document::Line,
104    element_indentaions_pts: &ElementIndentationsPoints,
105    time_of_day_strs: &screenplay_document::TimeOfDayCollection,
106    environment_strs: &screenplay_document::EnvironmentStrings,
107    r_marker: &String,
108) -> Option<SPType> {
109    use screenplay_document::SPType::*;
110    use screenplay_document::SceneHeadingElement;
111
112    let previous_element_type = match new_line.text_elements.last() {
113        None => SPType::NONE,
114        Some(e) => match &e.element_type {
115            None => SPType::NONE,
116            Some(t) => t.clone(),
117        },
118    };
119
120    //TODO: FIXME: Calculate actual character width from font metrics...
121    let char_width = pdf_word.font_size * 0.6; // should be ~7.2 for 12-point font
122    let position_tolerance: f64 = 0.01;
123
124    // check current line type ...
125    if (new_line.line_type == Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Line)))
126        && (pdf_word.position.x >= element_indentaions_pts.right)
127    {
128        if pdf_word.text == *r_marker {
129            return Some(SPType::SP_LINE_REVISION_MARKER);
130        }
131        return Some(SPType::SP_SCENENUM);
132    }
133
134    // first pass of Word Type -- check previous element types first
135    match previous_element_type {
136        // if previous type was "content" types...
137        SPType::SP_SCENE_HEADING(SceneHeadingElement::TimeOfDay) => {
138            if pdf_word.text == "-".to_string() {
139                return Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Separator));
140            }
141            return None;
142        }
143        SPType::SP_SCENE_HEADING(SceneHeadingElement::Separator) => {
144            let type_before_separator =
145                match new_line.text_elements.get(new_line.text_elements.len() - 2) {
146                    None => SPType::NONE,
147                    Some(t) => match &t.element_type {
148                        None => SPType::NONE,
149                        Some(e) => e.clone(),
150                    },
151                };
152
153            if time_of_day_strs.is_time_of_day(&pdf_word.text) {
154                return Some(SP_SCENE_HEADING(SceneHeadingElement::TimeOfDay));
155            }
156
157            match type_before_separator {
158                SPType::NONE => return None, // ? Something has gone very wrong...
159                SPType::SP_SCENE_HEADING(SceneHeadingElement::Location) => {
160                    return Some(SP_SCENE_HEADING(SceneHeadingElement::SubLocation));
161                }
162                SPType::SP_SCENE_HEADING(SceneHeadingElement::Location) => {
163                    if pdf_word.text == "-".to_string() {
164                        return Some(SP_SCENE_HEADING(SceneHeadingElement::Separator));
165                    }
166                    return Some(SP_SCENE_HEADING(SceneHeadingElement::SubLocation));
167                }
168                SPType::SP_SCENE_HEADING(SceneHeadingElement::TimeOfDay)
169                | SP_SCENE_HEADING(SceneHeadingElement::SlugOther) => {
170                    return Some(SP_SCENE_HEADING(SceneHeadingElement::SlugOther));
171                }
172                _ => {
173                    dbg!(&pdf_word.text);
174                    dbg!(type_before_separator);
175                    //panic!();
176                    return Some(SP_SCENE_HEADING(SceneHeadingElement::SlugOther));
177                }
178            }
179        }
180        SPType::SP_PARENTHETICAL => return Some(previous_element_type.clone()),
181        SPType::SP_SCENE_HEADING(SceneHeadingElement::SubLocation) => {
182            if pdf_word.text == "-".to_string() {
183                return Some(SP_SCENE_HEADING(SceneHeadingElement::Separator));
184            }
185            return Some(SP_SCENE_HEADING(SceneHeadingElement::SubLocation));
186        }
187        SPType::SP_SCENE_HEADING(SceneHeadingElement::Location) => {
188            if pdf_word.text == "-".to_string() {
189                return Some(SP_SCENE_HEADING(SceneHeadingElement::Separator));
190            }
191            return Some(SP_SCENE_HEADING(SceneHeadingElement::Location));
192        }
193        SPType::SP_SCENE_HEADING(SceneHeadingElement::Environment) => {
194            return Some(SP_SCENE_HEADING(SceneHeadingElement::Location));
195        }
196        SPType::SP_CHARACTER => {
197            // TODO:
198            // Create a function that checks all the "non-content-types"
199            // and returns that as an optional SPType
200            // then just match against that for every content type
201            // if it's non-content, then return that type
202            // else if it's content, then handle that within this block
203            if pdf_word.text.starts_with("(") {
204                return Some(SPType::SP_CHARACTER_EXTENSION);
205            } else {
206                if pdf_word.text == *r_marker {
207                    return Some(SPType::SP_LINE_REVISION_MARKER);
208                }
209                return Some(SPType::SP_CHARACTER);
210            }
211        }
212        SPType::SP_DD_L_CHARACTER => {
213            if pdf_word.text.starts_with("(") {
214                return Some(SPType::SP_DD_L_CHARACTER_EXTENSION);
215            } else {
216                return Some(SPType::SP_DD_L_CHARACTER);
217            }
218        }
219        SPType::SP_DD_R_CHARACTER => {
220            if pdf_word.text.starts_with("(") {
221                return Some(SPType::SP_DD_R_CHARACTER_EXTENSION);
222            } else {
223                return Some(SPType::SP_DD_R_CHARACTER);
224            }
225        }
226        SPType::SP_DD_L_CHARACTER_EXTENSION
227        | SPType::SP_DD_R_CHARACTER_EXTENSION
228        | SPType::SP_CHARACTER_EXTENSION => {
229            return Some(previous_element_type.clone());
230        }
231
232        _ => {
233            // ------------- INDENTATION PARSING --------------------------
234
235            // Within Vertical Content Zone after this point
236
237            //println!("{}", pdf_word.position.y - element_indentaions_pts.top);
238            if pdf_word.position.y < element_indentaions_pts.top
239                && pdf_word.position.y > element_indentaions_pts.bottom
240            {
241                //Check if it's a scene number
242                // TODO: This is a NAIVE implementation... probably need additional verification at some point...
243
244                if pdf_word.position.x < element_indentaions_pts.left {
245                    return Some(SPType::SP_SCENENUM);
246                }
247                /*
248                 */
249                else if pdf_word.position.x >= element_indentaions_pts.right {
250                    if pdf_word.text == *r_marker {
251                        return Some(SPType::SP_LINE_REVISION_MARKER);
252                    }
253                    return Some(SPType::SP_SCENENUM);
254                } else {
255                    let _within_tolerance = |target| {
256                        if (&pdf_word.position.x - &target).abs() > position_tolerance {
257                            return false;
258                        } else {
259                            return true;
260                        };
261                    };
262
263                    //Within Vertical AND Horizontal Content Zone after this point
264
265                    //ACTION
266                    if _within_tolerance(element_indentaions_pts.action) {
267                        //TODO: FIXME: Let user PASS IN INT_EXT PATTERNS (i.e. for non-english scripts)
268
269                        if let Some(_) = Environment::from_str(&pdf_word.text, environment_strs) {
270                            return Some(SP_SCENE_HEADING(SceneHeadingElement::Environment));
271                        } else if new_line.line_type == None {
272                            return Some(SPType::SP_ACTION);
273                        };
274                    }
275                    if _within_tolerance(element_indentaions_pts.character)
276                        && new_line.line_type == None
277                    {
278                        return Some(SPType::SP_CHARACTER);
279                    } else if _within_tolerance(element_indentaions_pts.dialogue)
280                        && new_line.line_type == None
281                    {
282                        return Some(SPType::SP_DIALOGUE);
283                    } else if _within_tolerance(element_indentaions_pts.parenthetical)
284                        && pdf_word.text.starts_with("(")
285                        && new_line.line_type == None
286                    {
287                        return Some(SPType::SP_PARENTHETICAL);
288                    } else {
289                        return None;
290                    }
291                };
292            }
293
294            // Text is either ABOVE the top margin or BELOW the bottom margins...
295            // pdf_word.text == "17A.".to_string() {println!("PAGENUMBER FOUND!----------------");}
296            if pdf_word.position.y >= element_indentaions_pts.top {
297                let wordwidth: f64 = char_width * f64::from(pdf_word.text.len() as i32);
298                let rightedge: f64 = wordwidth + pdf_word.position.x;
299                if pdf_word.position.x < element_indentaions_pts.pagewidth / 3.0 {
300                    return Some(SPType::NON_CONTENT_TOP);
301                } else if (element_indentaions_pts.pagewidth - pdf_word.position.x)
302                    < (element_indentaions_pts.pagewidth / 4.0)
303                    && (pdf_word.text.ends_with("."))
304                {
305                    return Some(SPType::SP_PAGENUM);
306                } else {
307                    return Some(SPType::NON_CONTENT_TOP);
308                }
309            }
310            // TODO: let user pass in MORE and CONTINUED strings as a struct
311            if pdf_word.text.contains("(MORE)")
312                | pdf_word.text.contains("(CONTINUED)")
313                | pdf_word.text.contains("(CONT'D)")
314            {
315                return Some(SPType::SP_MORE_CONTINUED);
316            } else {
317                return Some(SPType::NON_CONTENT_BOTTOM);
318            }
319        }
320    }
321}
322
323pub fn get_screenplay_doc_from_pdf_obj(
324    doc: pdf_document::PDFDocument,
325    element_indent_in_opt: Option<ElementIndentationsInches>,
326    rev_marker_opt: Option<String>,
327    time_of_day_strs_opt: Option<screenplay_document::TimeOfDayCollection>,
328    env_strs_opt: Option<EnvironmentStrings>,
329) -> Option<screenplay_document::ScreenplayDocument> {
330    use screenplay_document::ScreenplayDocument;
331
332    if doc.pages.len() < 1 {
333        return None;
334    }
335
336    let time_of_day_strs: screenplay_document::TimeOfDayCollection;
337    if let Some(tds) = time_of_day_strs_opt {
338        time_of_day_strs = tds;
339    } else {
340        time_of_day_strs = screenplay_document::TimeOfDayCollection::default();
341    }
342
343    let environment_strs: screenplay_document::EnvironmentStrings;
344    if let Some(evs) = env_strs_opt {
345        environment_strs = evs;
346    } else {
347        environment_strs = EnvironmentStrings::default();
348    }
349
350    let r_marker;
351    if let Some(rm) = rev_marker_opt {
352        r_marker = rm;
353    } else {
354        r_marker = "*".to_string();
355    }
356
357    let mut new_screenplay_doc: ScreenplayDocument = ScreenplayDocument::default();
358
359    for pdf_page in doc.pages.iter() {
360        if pdf_page.lines.len() < 1 {
361            continue;
362        };
363        // TODO: abstract out the "line handling" logic into "fn get_line()"??
364
365        let mut new_page = screenplay_document::Page::default();
366
367        let mut prev_line_y_pos: f64 = 0.0;
368        let mut line_height: f64 = 12.0; //This line height could be identified either here in-line or in
369        // a pre-processing scan of the document
370        // in-line might be better, to do it page-by-page as we go, rather than keep dictionaries/hashmaps
371
372        //TODO: the current resolution and element_indentations_pts don't have to be defined here
373        // in this for loop
374        // UNLESS we need to set a different resolution or indentations for different pages
375        // like a frankenscript from multiple writers
376        // We should let the user pass in multiple ranges of indentations, optionally
377        // but that's not necessary right now for basic functionality
378        let mut current_resolution: f64 = 72.0;
379        let element_indentaions_pts;
380        if let Some(ref indentations) = element_indent_in_opt {
381            element_indentaions_pts =
382                ElementIndentationsPoints::from_inches(indentations, &Some(current_resolution));
383        } else {
384            element_indentaions_pts =
385                ElementIndentationsPoints::us_letter_default(&Some(current_resolution));
386        }
387        for (pdf_l_idx, pdf_line) in pdf_page.lines.iter().enumerate() {
388            if pdf_line.words.len() < 1 {
389                continue;
390            };
391
392            let mut new_line = screenplay_document::Line::default();
393            let mut previous_element_type: SPType = SPType::NONE;
394            let mut word_counter: usize = 0;
395            for pdf_word in pdf_line.words.iter() {
396                //println!("Iterating over PDF WORDS!");
397                let mut new_text_element = screenplay_document::TextElement::default();
398
399                let new_word_type: Option<SPType> = _get_type_for_word(
400                    &pdf_word,
401                    &new_line,
402                    &element_indentaions_pts,
403                    &time_of_day_strs,
404                    &environment_strs,
405                    &r_marker,
406                );
407
408                //println!("New type! {:?}", new_word_type);
409                new_text_element.element_position = Some(pdf_word.position.clone());
410
411                if let Some(nwt) = new_word_type {
412                    match nwt {
413                        // Assign proper LINE TYPEs based on current WORD type
414                        SPType::SP_DIALOGUE => {
415                            if new_line.line_type == None {
416                                new_line.line_type = Some(SPType::SP_DIALOGUE);
417                            }
418                        }
419                        SPType::SP_PARENTHETICAL => {
420                            if new_line.line_type == None {
421                                new_line.line_type = Some(SPType::SP_PARENTHETICAL);
422                            }
423                        }
424                        SPType::SP_DD_L_PARENTHETICAL
425                        | SPType::SP_DD_R_PARENTHETICAL
426                        | SPType::SP_DD_L_DIALOGUE
427                        | SPType::SP_DD_R_DIALOGUE => {
428                            new_line.line_type = Some(SPType::SP_DUAL_DIALOGUES);
429                        }
430                        SPType::SP_CHARACTER => {
431                            if new_line.line_type == None {
432                                new_line.line_type = Some(SPType::SP_CHARACTER);
433                            }
434                        }
435                        SPType::SP_DD_L_CHARACTER | SPType::SP_DD_R_CHARACTER => {
436                            if new_line.line_type == None {
437                                new_line.line_type = Some(SPType::SP_DUAL_CHARACTERS);
438                            }
439                        }
440                        SPType::SP_ACTION => {
441                            if new_line.line_type == None {
442                                new_line.line_type = Some(SPType::SP_ACTION);
443                            }
444                        }
445                        SPType::SP_SCENE_HEADING(SceneHeadingElement::Environment) => {
446                            use screenplay_document::SceneHeadingElement;
447                            if new_line.line_type == None {
448                                new_line.line_type =
449                                    Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Line));
450                            }
451                        }
452
453                        //SPECIAL CASES -- still add these elements to the line, but
454                        // ALSO update the relevant metadata
455                        // any element needs to still be available in the screenplay document, so we can
456                        // re-assign it after parsing, if necessary
457                        SPType::SP_PAGENUM => {
458                            if new_line.line_type == None {
459                                new_line.line_type = Some(SPType::SP_PAGE_HEADER);
460                            }
461                            new_page.page_number = Some(PageNumber(pdf_word.text.clone()));
462                            //continue;
463                        }
464                        SPType::SP_PAGE_REVISION_LABEL => {
465                            // TODO: parse revision label for COLOR and DATE
466                            // then ADD metadata to PAGE
467                            new_page.revised = true;
468                            //continue;
469                        }
470                        SPType::NON_CONTENT_TOP
471                        | SPType::NON_CONTENT_BOTTOM
472                        | SPType::NON_CONTENT_LEFT
473                        | SPType::NON_CONTENT_RIGHT => {
474                            //println!("Non-Content!!!!!");
475                            //println!("Current action margin: {}", element_indentaions_pts.action);
476                            //println!("{} | {}", new_text_element.element_position.unwrap().x, new_text_element.element_position.unwrap().y);
477                            //continue;
478                        }
479                        SPType::SP_SCENENUM => {
480                            //println!(" ---------SCENE NUMBER -------");
481
482                            if pdf_word.text.contains(&r_marker) {
483                                new_line.revised = true;
484                            }
485                            let maybe_scene_num = Some(
486                                pdf_word
487                                    .text
488                                    .trim_matches('*') //FIXME: This DOESN'T trim out the arbitrary user-defined revision marker! only asterisk! Fix this!
489                                    .to_string()
490                                    .trim_matches('.')
491                                    .to_string(),
492                            );
493                            if let Some(sn) = maybe_scene_num {
494                                if !sn.is_empty() {
495                                    new_line.scene_number = Some(sn);
496                                    use screenplay_document::SceneHeadingElement;
497                                    match new_line.line_type {
498                                        Some(SPType::NONE) => {
499                                            new_line.line_type = Some(SPType::SP_SCENE_HEADING(
500                                                SceneHeadingElement::Line,
501                                            ));
502                                        }
503                                        _ => {}
504                                    }
505                                    previous_element_type = SPType::SP_SCENENUM;
506                                }
507                            }
508
509                            //continue;
510                        }
511                        SPType::SP_LINE_REVISION_MARKER => {
512                            new_line.revised = true;
513                            previous_element_type = SPType::SP_LINE_REVISION_MARKER;
514                            //continue;
515                        }
516
517                        _ => {}
518                    }
519                }
520
521                new_text_element.element_type = new_word_type.clone();
522                new_text_element.text = pdf_word.text.clone();
523
524                // -------- WHITESPACING --------
525
526                // CALCULATE PRECEDING WHITESPACE CHARS, IF ANY
527
528                if word_counter > 0 {
529                    if let Some(last_word) = pdf_line.words.last() {
530                        let char_width: f64 = 7.2;
531                        let whitespace_chars: u64 = u64::from(
532                            ((pdf_word.position.x - (last_word.position.x + last_word.bbox_width))
533                                / char_width)
534                                .round() as u64,
535                        );
536
537                        if whitespace_chars >= 1 {
538                            match previous_element_type {
539                                SPType::SP_SCENENUM | SPType::SP_LINE_REVISION_MARKER => {
540                                    new_text_element.preceding_whitespace_chars = 0;
541                                }
542                                _ => {
543                                    new_text_element.preceding_whitespace_chars = whitespace_chars;
544                                }
545                            }
546                        } else {
547                            //FIXME: WTF does this whole if block even do???? Why does this print so often???
548                            //println!("NEW TEXT ELEMENT OVERLAPS PREVIOUS ELEMENT! Assigned 1 unit of preceding whtiespace...");
549                            new_text_element.preceding_whitespace_chars = 1
550                        }
551                    };
552                }
553                if let Some(new_type) = new_word_type.clone() {
554                    previous_element_type = new_type;
555                } else {
556                    previous_element_type = SPType::NONE;
557                }
558
559                new_line.text_elements.push(new_text_element);
560                //println!("Pushing new text element!");
561
562                word_counter += 1;
563            }
564            //Add number of preceding blank lines to this line
565            let cur_y_pos = pdf_line.words.first().unwrap().position.y;
566            if prev_line_y_pos > 1.0 {
567                let y_delta = prev_line_y_pos - cur_y_pos;
568                if y_delta > line_height { // 12.0 default...
569                    let blank_lines_count: u64 = (y_delta / line_height).round() as u64 - 1;
570                    new_line.preceding_empty_lines = blank_lines_count;
571                }
572            }
573
574            prev_line_y_pos = cur_y_pos;
575            if new_line.text_elements.is_empty() {
576                continue;
577            }
578
579            match new_line.line_type {
580                None => {}
581                // CHARACTER PARSING
582                Some(SPType::SP_CHARACTER) => {
583                    let mut character_name = String::new();
584                    for element in &new_line.text_elements {
585                        if element.element_type == Some(SPType::SP_CHARACTER) {
586                            if !character_name.is_empty() {
587                                character_name.push(' ');
588                            }
589                            character_name.push_str(&element.text);
590                        }
591                    }
592                    //panic!();
593                    
594                    let mut character_exists_in_doc = false;
595                    if !&new_screenplay_doc.characters.is_empty() {
596                        for character in &new_screenplay_doc.characters {
597                            if character.name == character_name {
598                                character_exists_in_doc = true;
599                            }
600                        }
601                    }
602                    if !character_exists_in_doc{
603                        
604                        let new_character = Character {
605                            name: character_name,
606                            id: screenplay_document::CharacterID::new(),
607                        };
608                        new_screenplay_doc.characters.insert(new_character);
609                    }
610                }
611                // SCENE / LOCATION PARSING
612                Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Line)) => {
613                    // Environment Parsing
614                    let maybe_first_word = &new_line
615                        .text_elements
616                        .iter()
617                        .filter(|te| {
618                            te.element_type
619                                == Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Environment))
620                        })
621                        .take(1)
622                        .next();
623
624                    let mut new_line_env = Environment::Ext;
625
626                    if let Some(fw) = maybe_first_word {
627                        new_line_env = Environment::from_str(&fw.text, &environment_strs).unwrap();
628                    }
629
630                    // Location Parsing
631
632                    let mut root_location_string: String = String::new();
633
634                    let mut current_sub_location_string = String::new();
635                    let mut full_path: Vec<String> = Vec::new();
636                    let mut root_location_done = false;
637
638                    for element in &new_line.text_elements {
639                        match element.element_type {
640                            Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Location))
641                            | Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Environment)) => {
642                                if !root_location_string.is_empty() {
643                                    root_location_string.push(' ');
644                                }
645                                root_location_string.push_str(&element.text.clone());
646                            }
647                            Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Separator)) => {
648                                if root_location_done {
649                                    if !current_sub_location_string.is_empty() {
650                                        full_path.push(current_sub_location_string.clone());
651                                        current_sub_location_string = String::new();
652                                    }
653                                } else {
654                                    root_location_done = true;
655                                    full_path.push(root_location_string.clone());
656                                }
657                            }
658                            Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::SubLocation)) => {
659                                if !current_sub_location_string.is_empty() {
660                                    current_sub_location_string.push(' ');
661                                }
662                                current_sub_location_string.push_str(&element.text.clone());
663                            }
664                            _ => {
665                                if !root_location_string.is_empty() {
666                                    break;
667                                }
668                            }
669                        }
670                    }
671
672                    let mut location_id_to_insert: Option<LocationID> = None;
673                    let mut exists: bool = false;
674
675                    for (existing_id, existing_root) in &new_screenplay_doc.locations {
676                        if existing_root.superlocation.is_some() {
677                            continue;
678                        }
679                        if root_location_string == existing_root.string {
680                            location_id_to_insert = Some(existing_id.clone());
681                            exists = true;
682                        }
683                    }
684
685                    if !exists {
686                        location_id_to_insert = Some(LocationID::new());
687
688                        let new_root_location: LocationNode = LocationNode {
689                            string: root_location_string.clone(),
690                            sublocations: HashSet::new(),
691                            superlocation: None,
692                        };
693                        new_screenplay_doc
694                            .locations
695                            .insert(location_id_to_insert.clone().unwrap(), new_root_location);
696                    }
697
698                    // Subpath parsing and insertion
699
700                    if let Some((id, s_path)) =
701                        crate::reports::location_path_exists(&new_screenplay_doc, &full_path)
702                        
703                    {
704                        let mut current_id = id.clone();
705
706                        for pathstring in s_path {
707                            if let Some(location) =
708                                &mut new_screenplay_doc.locations.get_mut(&current_id)
709                            {
710                                let new_id = LocationID::new();
711
712                                let new_location = LocationNode {
713                                    string: pathstring.clone(),
714                                    sublocations: HashSet::new(),
715                                    superlocation: Some(current_id.clone()),
716                                };
717                                current_id = new_id.clone();
718                                location_id_to_insert = Some(new_id.clone());
719                                location.add_sublocation(new_id.clone());
720                                new_screenplay_doc
721                                    .locations
722                                    .insert(new_id.clone(), new_location);
723                            }
724                        }
725                    }
726
727                    // Scene Insertion
728                    let new_scene = Scene {
729                        number: {
730                            if let Some(num) = &new_line.scene_number.clone() {
731                                Some(SceneNumber(num.clone()))
732                            } else {
733                                None
734                            }
735                        },
736                        environment: new_line_env,
737                        start: ScreenplayCoordinate {
738                            page: new_screenplay_doc.pages.len(),
739                            line: new_page.lines.len(),
740                            element: None,
741                        },
742                        revised: new_line.revised,
743                        story_locations: {
744                            if let Some(id) = location_id_to_insert {
745                                vec![id.clone()]
746                            } else {
747                                Vec::new()
748                            }
749                        },
750                        story_time_of_day: {
751                            let maybe_time: Vec<TextElement> = new_line
752                                .text_elements
753                                .iter()
754                                .filter(|el| {
755                                    el.element_type
756                                        == Some(SPType::SP_SCENE_HEADING(
757                                            SceneHeadingElement::TimeOfDay,
758                                        ))
759                                })
760                                .map(|el| el.clone())
761                                .collect();
762                            match maybe_time.is_empty() {
763                                true => None,
764                                false => time_of_day_strs
765                                    .get_time_of_day(&maybe_time.first().unwrap().text),
766                            }
767                        },
768                    };
769                    let new_scene_id = SceneID::new();
770                    new_line.scene_id = Some(new_scene_id.clone());
771                    new_screenplay_doc.scenes.insert(new_scene_id, new_scene);
772                }
773                _ => {}
774            }
775
776            // line number fixing
777            if let Some(SPType::SP_SCENE_HEADING(SceneHeadingElement::Line)) = new_line.line_type {
778                for te in &mut new_line.text_elements {
779                    if te.element_type == None
780                        && te.text == new_line.scene_number.clone().unwrap_or("_N?N_".to_string())
781                    {
782                        te.element_type = Some(SPType::SP_SCENENUM);
783                        println!("{:?}", te.element_type);
784                    }
785                }
786            } else {
787                // Text Element Fixing -- overwrite previous NONE-TYPED elements to the LINE TYPE
788                // if it's content
789                // else leave it alone
790                let mut last_element_type: &Option<SPType> = &None;
791                for te in &mut new_line.text_elements {
792                    if te.element_type == None {
793                        match new_line.line_type {
794                            Some(SPType::SP_ACTION) => {
795                                te.element_type = Some(SPType::SP_ACTION);
796                            }
797                            Some(SPType::SP_CHARACTER) => match last_element_type {
798                                Some(SPType::SP_CHARACTER_EXTENSION) => {
799                                    te.element_type = Some(SPType::SP_CHARACTER_EXTENSION);
800                                }
801                                _ => {
802                                    te.element_type = Some(SPType::SP_CHARACTER);
803                                }
804                            },
805                            Some(SPType::SP_DIALOGUE) => {
806                                te.element_type = Some(SPType::SP_DIALOGUE);
807                            }
808
809                            _ => {}
810                        }
811                    }
812
813                    if new_line.line_type == Some(SPType::SP_PAGE_HEADER)
814                        && te.element_type == Some(SPType::NON_CONTENT_TOP)
815                    {
816                        te.element_type = Some(SPType::SP_PAGE_REVISION_LABEL);
817                    }
818                    last_element_type = &te.element_type
819                }
820            }
821
822            new_page.lines.push(new_line);
823        }
824        if new_page.lines.is_empty() {
825            continue;
826        }
827
828        new_screenplay_doc.pages.push(new_page);
829    }
830
831    Some(new_screenplay_doc)
832}