use std::collections::HashMap;
pub const COORDINATE_MINIMUM: usize = 1; pub const COORDINATE_MAXIMUM: usize = 1000; pub const LIBRARY_BREAK: char = '\x01'; pub const MORE_COWBELL: char = '\x07'; pub const LINE_BREAK: char = '\x0A'; pub const SCROLL_BREAK: char = '\x17'; pub const SECTION_BREAK: char = '\x18'; pub const CHAPTER_BREAK: char = '\x19'; pub const BOOK_BREAK: char = '\x1A'; pub const VOLUME_BREAK: char = '\x1C'; pub const COLLECTION_BREAK: char = '\x1D'; pub const SERIES_BREAK: char = '\x1E'; pub const SHELF_BREAK: char = '\x1F';
pub const ADDRESS_MICRO_BREAK: u8 = b'.'; pub const ADDRESS_MACRO_BREAK: u8 = b'/'; pub const ADDRESS_MACRO_ALT: u8 = b';';
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Copy, Clone)]
pub struct ZCoordinate {
pub library: usize,
pub shelf: usize,
pub series: usize
}
impl ZCoordinate {
pub fn new() -> ZCoordinate {
ZCoordinate { library: 1, shelf: 1, series: 1 }
}
}
impl Default for ZCoordinate {
fn default() -> ZCoordinate {
ZCoordinate::new()
}
}
impl std::fmt::Display for ZCoordinate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "{}.{}.{}", self.library, self.shelf, self.series);
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Copy, Clone)]
pub struct YCoordinate {
pub collection: usize,
pub volume: usize,
pub book: usize
}
impl YCoordinate {
pub fn new() -> YCoordinate {
YCoordinate { collection: 1, volume: 1, book: 1 }
}
}
impl Default for YCoordinate {
fn default() -> YCoordinate {
YCoordinate::new()
}
}
impl std::fmt::Display for YCoordinate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "{}.{}.{}", self.collection, self.volume, self.book);
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Copy, Clone)]
pub struct XCoordinate {
pub chapter: usize,
pub section: usize,
pub scroll: usize
}
impl XCoordinate {
pub fn new() -> XCoordinate {
XCoordinate { chapter: 1, section: 1, scroll: 1 }
}
}
impl Default for XCoordinate {
fn default() -> XCoordinate {
XCoordinate::new()
}
}
impl std::fmt::Display for XCoordinate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "{}.{}.{}", self.chapter, self.section, self.scroll);
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
pub struct Coordinate {
pub z: ZCoordinate,
pub y: YCoordinate,
pub x: XCoordinate,
}
impl Coordinate {
pub fn new() -> Coordinate {
Coordinate { z: ZCoordinate::new(), y: YCoordinate::new(), x: XCoordinate::new() }
}
}
impl std::fmt::Display for Coordinate {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "{}/{}/{}", self.z, self.y, self.x);
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, PartialOrd, Clone)]
pub struct PositionedScroll {
pub coord: Coordinate,
pub scroll: String
}
impl PositionedScroll {
pub fn new() -> PositionedScroll {
PositionedScroll { coord: to_coordinate("1.1.1/1.1.1/1.1.1"), scroll: "".to_string() }
}
}
impl std::fmt::Display for PositionedScroll {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "{}: {}", self.coord.to_string(), self.scroll[..4].to_string());
}
}
impl Ord for PositionedScroll {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
return self.coord.cmp(&other.coord);
}
}
#[derive(Default, Debug, PartialEq, PartialOrd, Copy, Clone)]
pub struct Range {
pub start: Coordinate,
pub end: Coordinate
}
impl Range {
pub fn new() -> Range {
Range { start: to_coordinate("1.1.1/1.1.1/1.1.1"), end: to_coordinate("1.1.1/1.1.1/1.1.1") }
}
}
impl std::fmt::Display for Range {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "{}-{}", self.start, self.end);
}
}
#[derive(Default, Debug, Clone)]
pub struct PhextParseError;
impl std::error::Error for PhextParseError {}
impl std::fmt::Display for PhextParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return write!(f, "Phext addresses are of the form LB.SF.SR/CN.VM.BK/CH.SN.SC");
}
}
impl std::convert::TryFrom<&str> for Coordinate {
type Error = PhextParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let parts: Vec<&str> = value.split('/').collect();
let error: PhextParseError = Default::default();
if parts.len() != 3 {
return Err(error);
}
let z: Vec<&str> = parts[0].split('.').collect();
let y: Vec<&str> = parts[1].split('.').collect();
let x: Vec<&str> = parts[2].split('.').collect();
if z.len() != 3 || y.len() != 3 || x.len() != 3 {
return Err(error);
}
let mut result: Coordinate = Default::default();
result.z.library = z[0].parse::<usize>().expect("Library missing");
result.z.shelf = z[1].parse::<usize>().expect("Shelf missing");
result.z.series = z[2].parse::<usize>().expect("Series missing");
result.y.collection = y[0].parse::<usize>().expect("Collection missing");
result.y.volume = y[1].parse::<usize>().expect("Volume missing");
result.y.book = y[2].parse::<usize>().expect("Book missing");
result.x.chapter = x[0].parse::<usize>().expect("Chapter missing");
result.x.section = x[1].parse::<usize>().expect("Section missing");
result.x.scroll = x[2].parse::<usize>().expect("Scroll missing");
return Ok(result);
}
}
pub fn check_for_cowbell(phext: &str) -> bool {
for byte in phext.as_bytes() {
if *byte == MORE_COWBELL as u8 {
return true;
}
}
return false;
}
pub fn get_subspace_coordinates(subspace: &[u8], target: Coordinate) -> (usize, usize, Coordinate) {
let mut walker: Coordinate = default_coordinate();
let mut best: Coordinate = default_coordinate();
let mut subspace_index: usize = 0;
let mut start: usize = 0;
let mut end: usize = 0;
let mut stage = 0;
let max = subspace.len();
while subspace_index < max {
let next = subspace[subspace_index];
let compare = next as char;
if stage == 0 {
if walker == target {
stage = 1;
start = subspace_index;
best = walker;
}
if walker < target {
best = walker;
}
}
if stage < 2 && walker > target {
if stage == 0 {
start = subspace_index - 1;
}
end = subspace_index - 1;
stage = 2;
}
if is_phext_break(next) {
if compare == SCROLL_BREAK { walker.scroll_break(); }
if compare == SECTION_BREAK { walker.section_break(); }
if compare == CHAPTER_BREAK { walker.chapter_break(); }
if compare == BOOK_BREAK { walker.book_break(); }
if compare == VOLUME_BREAK { walker.volume_break(); }
if compare == COLLECTION_BREAK { walker.collection_break(); }
if compare == SERIES_BREAK { walker.series_break(); }
if compare == SHELF_BREAK { walker.shelf_break(); }
if compare == LIBRARY_BREAK { walker.library_break(); }
}
if stage < 2 && walker > target {
end = subspace_index;
stage = 2;
}
subspace_index += 1;
}
if stage == 1 && walker == target {
end = max;
stage = 2;
}
if stage == 0 {
start = max;
end = max;
}
return (start, end, best);
}
pub fn remove(phext: &str, location: Coordinate) -> String {
let phase1 = replace(phext, location, "");
return normalize(phase1.as_str());
}
pub fn create_summary(phext: &str) -> String {
let mut summary = String::new();
for (_i, ch) in phext.char_indices() {
if is_phext_break(ch as u8) {
break;
}
summary.push(ch);
}
if summary.len() < phext.len() {
summary.push_str("...");
}
return summary;
}
pub fn navmap(urlbase: &str, phext: &str) -> String {
let phokens = phokenize(phext);
let mut result = String::new();
let max = phokens.len();
if max > 0 {
result += "<ul>\n";
}
for phoken in phokens {
result += &format!("<li><a href=\"{}{}\">{} {}</a></li>\n", urlbase, phoken.coord.to_urlencoded(), phoken.coord.to_string(), create_summary(&phoken.scroll)).to_string();
}
if max > 0 {
result += "</ul>\n";
}
return result;
}
pub fn textmap(phext: &str) -> String {
let phokens = phokenize(phext);
let mut result = String::new();
for phoken in phokens {
result += &format!("* {}: {}\n", phoken.coord.to_string(), create_summary(&phoken.scroll)).to_string();
}
return result;
}
pub fn checksum(phext: &str) -> String {
let buffer = phext.as_bytes();
let hash = xxhash_rust::xxh3::xxh3_128(buffer);
return format!("{:0>32}", format!("{:x}", hash));
}
pub fn manifest(phext: &str) -> String {
let mut phokens = phokenize(phext);
let mut i = 0;
while i < phokens.len() {
phokens[i].scroll = checksum(phokens[i].scroll.as_str());
i += 1;
}
let result = dephokenize(&mut phokens);
return result;
}
fn soundex_internal(byte: String) -> String {
let letter1 = "bpfv";
let letter2 = "cskgjqxz";
let letter3 = "dt";
let letter4 = "l";
let letter5 = "mn";
let letter6 = "r";
let mut value: usize = 1; for c in byte.to_string().into_bytes() {
if letter1.contains(c as char) { value += 1; continue; }
if letter2.contains(c as char) { value += 2; continue; }
if letter3.contains(c as char) { value += 3; continue; }
if letter4.contains(c as char) { value += 4; continue; }
if letter5.contains(c as char) { value += 5; continue; }
if letter6.contains(c as char) { value += 6; continue; }
}
return (value % 99).to_string();
}
pub fn soundex_v1(phext: &str) -> String {
let mut phokens = phokenize(phext);
for ith in &mut phokens {
ith.scroll = soundex_internal(ith.scroll.clone());
}
return dephokenize(&mut phokens);
}
fn index_phokens(phext: &str) -> Vec<PositionedScroll> {
let phokens = phokenize(phext);
let mut offset: usize = 0;
let mut coord = default_coordinate();
let mut output: Vec<PositionedScroll> = Vec::new();
let mut i: usize = 0;
while i < phokens.len() {
let reference = phokens[i].coord;
while coord.z.library < reference.z.library { coord.library_break(); offset += 1; }
while coord.z.shelf < reference.z.shelf { coord.shelf_break(); offset += 1; }
while coord.z.series < reference.z.series { coord.series_break(); offset += 1; }
while coord.y.collection < reference.y.collection { coord.collection_break(); offset += 1; }
while coord.y.volume < reference.y.volume { coord.volume_break(); offset += 1; }
while coord.y.book < reference.y.book { coord.book_break(); offset += 1; }
while coord.x.chapter < reference.x.chapter { coord.chapter_break(); offset += 1; }
while coord.x.section < reference.x.section { coord.section_break(); offset += 1; }
while coord.x.scroll < reference.x.scroll { coord.scroll_break(); offset += 1; }
output.push(PositionedScroll { coord, scroll: format!("{}", offset)});
offset += phokens[i].scroll.len();
i += 1;
}
return output;
}
pub fn index(phext: &str) -> String {
let mut output = index_phokens(phext);
return dephokenize(&mut output);
}
pub fn offset(phext: &str, coord: Coordinate) -> usize {
let mut output = index_phokens(phext);
let mut best = default_coordinate();
let mut matched = false;
let mut fetch_coord = coord;
for phoken in output.clone() {
if phoken.coord <= coord {
best = phoken.coord;
}
if phoken.coord == coord {
matched = true;
}
}
if matched == false {
fetch_coord = best;
}
let index = dephokenize(&mut output);
return fetch(index.as_str(), fetch_coord).parse::<usize>().unwrap();
}
pub fn replace(phext: &str, location: Coordinate, scroll: &str) -> String {
let bytes = phext.as_bytes();
let parts = get_subspace_coordinates(bytes, location);
let start: usize = parts.0;
let mut end: usize = parts.1;
let mut fixup: Vec<u8> = vec![];
let mut subspace_coordinate: Coordinate = parts.2;
while subspace_coordinate.z.library < location.z.library {
fixup.push(LIBRARY_BREAK as u8);
subspace_coordinate.library_break();
}
while subspace_coordinate.z.shelf < location.z.shelf {
fixup.push(SHELF_BREAK as u8);
subspace_coordinate.shelf_break();
}
while subspace_coordinate.z.series < location.z.series {
fixup.push(SERIES_BREAK as u8);
subspace_coordinate.series_break();
}
while subspace_coordinate.y.collection < location.y.collection {
fixup.push(COLLECTION_BREAK as u8);
subspace_coordinate.collection_break();
}
while subspace_coordinate.y.volume < location.y.volume {
fixup.push(VOLUME_BREAK as u8);
subspace_coordinate.volume_break();
}
while subspace_coordinate.y.book < location.y.book {
fixup.push(BOOK_BREAK as u8);
subspace_coordinate.book_break();
}
while subspace_coordinate.x.chapter < location.x.chapter {
fixup.push(CHAPTER_BREAK as u8);
subspace_coordinate.chapter_break();
}
while subspace_coordinate.x.section < location.x.section {
fixup.push(SECTION_BREAK as u8);
subspace_coordinate.section_break();
}
while subspace_coordinate.x.scroll < location.x.scroll {
fixup.push(SCROLL_BREAK as u8);
subspace_coordinate.scroll_break();
}
let text: std::slice::Iter<u8> = scroll.as_bytes().iter();
let max = bytes.len();
if end > max { end = max; }
let left = &bytes[..start];
let right = &bytes[end..];
let temp:Vec<u8> = left.iter().chain(fixup.iter()).chain(text).chain(right.iter()).cloned().collect();
let result: String = String::from_utf8(temp).expect("invalid utf8");
return result;
}
pub fn range_replace(phext: &str, location: Range, scroll: &str) -> String {
let bytes = phext.as_bytes();
let parts_start = get_subspace_coordinates(bytes, location.start);
let parts_end = get_subspace_coordinates(bytes, location.end);
println!("Start: {} vs {}", location.start, parts_start.2.to_string());
println!("End: {} vs {}", location.end, parts_end.2.to_string());
let start: usize = parts_start.0;
let mut end: usize = parts_end.1;
println!("Subspace start: {}, end: {}", start, end);
let text: std::slice::Iter<u8> = scroll.as_bytes().iter();
let max = bytes.len();
if end > max { end = max; }
let left = &bytes[..start];
let right = &bytes[end..];
let temp:Vec<u8> = left.iter().chain(text).chain(right.iter()).cloned().collect();
let result: String = String::from_utf8(temp).expect("invalid utf8");
return result;
}
pub fn insert(phext: String, location: Coordinate, scroll: &str) -> String {
let bytes: &[u8] = phext.as_bytes();
let parts: (usize, usize, Coordinate) = get_subspace_coordinates(bytes, location);
let end: usize = parts.1;
let mut fixup: Vec<u8> = vec![];
let mut subspace_coordinate: Coordinate = parts.2;
while subspace_coordinate.z.library < location.z.library {
fixup.push(LIBRARY_BREAK as u8);
subspace_coordinate.library_break();
}
while subspace_coordinate.z.shelf < location.z.shelf {
fixup.push(SHELF_BREAK as u8);
subspace_coordinate.shelf_break();
}
while subspace_coordinate.z.series < location.z.series {
fixup.push(SERIES_BREAK as u8);
subspace_coordinate.series_break();
}
while subspace_coordinate.y.collection < location.y.collection {
fixup.push(COLLECTION_BREAK as u8);
subspace_coordinate.collection_break();
}
while subspace_coordinate.y.volume < location.y.volume {
fixup.push(VOLUME_BREAK as u8);
subspace_coordinate.volume_break();
}
while subspace_coordinate.y.book < location.y.book {
fixup.push(BOOK_BREAK as u8);
subspace_coordinate.book_break();
}
while subspace_coordinate.x.chapter < location.x.chapter {
fixup.push(CHAPTER_BREAK as u8);
subspace_coordinate.chapter_break();
}
while subspace_coordinate.x.section < location.x.section {
fixup.push(SECTION_BREAK as u8);
subspace_coordinate.section_break();
}
while subspace_coordinate.x.scroll < location.x.scroll {
fixup.push(SCROLL_BREAK as u8);
subspace_coordinate.scroll_break();
}
let text: std::slice::Iter<u8> = scroll.as_bytes().iter();
let left = &bytes[..end];
let right = &bytes[end..];
let mut temp = Vec::with_capacity(left.len() + fixup.len() + text.len() + right.len());
temp.extend_from_slice(left);
temp.extend_from_slice(fixup.as_slice());
temp.extend_from_slice(text.as_slice());
temp.extend_from_slice(right);
let result: String = String::from_utf8(temp).expect("invalid utf8");
return result;
}
pub fn next_scroll(phext: &str, start: Coordinate) -> (PositionedScroll, Coordinate, String) {
let mut location = start;
let p = phext.as_bytes();
let mut output: Vec<u8> = vec![];
let mut remaining: Vec<u8> = vec![];
let mut pi: usize = 0;
let mut begin: Coordinate = start;
let pmax = p.len();
while pi < pmax
{
let test = p[pi] as char;
let mut dimension_break: bool = false;
if test == SCROLL_BREAK { location.scroll_break(); dimension_break = true; }
if test == SECTION_BREAK { location.section_break(); dimension_break = true; }
if test == CHAPTER_BREAK { location.chapter_break(); dimension_break = true; }
if test == BOOK_BREAK { location.book_break(); dimension_break = true; }
if test == VOLUME_BREAK { location.volume_break(); dimension_break = true; }
if test == COLLECTION_BREAK { location.collection_break(); dimension_break = true; }
if test == SERIES_BREAK { location.series_break(); dimension_break = true; }
if test == SHELF_BREAK { location.shelf_break(); dimension_break = true; }
if test == LIBRARY_BREAK { location.library_break(); dimension_break = true; }
if dimension_break {
if output.len() > 0 {
pi += 1;
break;
}
} else {
begin = location;
output.push(p[pi]);
}
pi += 1;
}
while pi < p.len()
{
remaining.push(p[pi]);
pi += 1;
}
let out_scroll: PositionedScroll = PositionedScroll{coord: begin, scroll: String::from_utf8(output).expect("valid UTF-8")};
return (out_scroll, location, String::from_utf8(remaining).expect("valid UTF-8"));
}
pub fn phokenize(phext: &str) -> Vec<PositionedScroll> {
let mut result: Vec<PositionedScroll> = Vec::new();
let mut coord = default_coordinate();
let mut temp: String = phext.to_string();
loop {
let item: PositionedScroll;
(item, coord, temp) = next_scroll(temp.as_str(), coord);
result.push(item);
if temp.len() == 0 { break; }
}
return result;
}
pub fn explode(phext: &str) -> HashMap<Coordinate, String> {
let parts = phokenize(phext);
let mut hash = HashMap::new();
for row in parts {
hash.insert(row.coord, row.scroll);
}
return hash;
}
pub fn implode(map: HashMap::<Coordinate, String>) -> String {
let mut vec: Vec<PositionedScroll> = Vec::new();
for (key, value) in map.into_iter() {
let ps = PositionedScroll{coord: key, scroll: value};
vec.push(ps);
}
vec.sort();
return dephokenize(&mut vec);
}
pub fn merge(left: &str, right: &str) -> String {
let tl = phokenize(left);
let tr = phokenize(right);
let mut tli = 0;
let mut tri = 0;
let maxtl = tl.len();
let maxtr = tr.len();
let mut result: String = Default::default();
let mut coord = default_coordinate();
loop {
let have_left = tli < maxtl;
let have_right = tri < maxtr;
let pick_left = have_left && (have_right == false || tl[tli].coord <= tr[tri].coord);
let pick_right = have_right && (have_left == false || tr[tri].coord <= tl[tli].coord);
if pick_left {
result.push_str(&append_scroll(tl[tli].clone(), coord));
coord = tl[tli].coord;
tli += 1;
}
if pick_right {
result.push_str(&append_scroll(tr[tri].clone(), coord));
coord = tr[tri].coord;
tri += 1;
}
if pick_left == false && pick_right == false {
break;
}
}
return result;
}
pub fn fetch(phext: &str, target: Coordinate) -> String {
let bytes: &[u8] = phext.as_bytes();
let parts = get_subspace_coordinates(bytes, target);
let start = parts.0 as usize;
let end = parts.1 as usize;
if end > start
{
let glyphs: usize = end - start;
let temp: Vec<u8> = bytes.iter().skip(start).take(glyphs).cloned().collect();
let result: String = String::from_utf8(temp).expect("invalid utf8");
return result;
}
return "".to_owned();
}
pub fn expand(phext: &str) -> String {
let mut copy = phext.to_string().clone();
unsafe {
let buffer = copy.as_bytes_mut();
let max = buffer.len();
let mut p: usize = 0;
loop {
if p == max { break; }
if buffer[p] == LINE_BREAK as u8 {
buffer[p] = SCROLL_BREAK as u8;
} else if buffer[p] == SCROLL_BREAK as u8 {
buffer[p] = SECTION_BREAK as u8;
} else if buffer[p] == SECTION_BREAK as u8 {
buffer[p] = CHAPTER_BREAK as u8;
} else if buffer[p] == CHAPTER_BREAK as u8 {
buffer[p] = BOOK_BREAK as u8;
} else if buffer[p] == BOOK_BREAK as u8 {
buffer[p] = VOLUME_BREAK as u8;
} else if buffer[p] == VOLUME_BREAK as u8{
buffer[p] = COLLECTION_BREAK as u8;
} else if buffer[p] == COLLECTION_BREAK as u8 {
buffer[p] = SERIES_BREAK as u8;
} else if buffer[p] == SERIES_BREAK as u8 {
buffer[p] = SHELF_BREAK as u8;
} else if buffer[p] == SHELF_BREAK as u8 {
buffer[p] = LIBRARY_BREAK as u8;
}
p += 1;
}
let temp: Vec<u8> = buffer.iter().cloned().collect();
let result: String = String::from_utf8(temp).expect("invalid utf8");
return result;
}
}
pub fn contract(phext: &str) -> String {
let mut copy = phext.to_string().clone();
unsafe {
let buffer = copy.as_bytes_mut();
let max = buffer.len();
let mut p: usize = 0;
loop {
if p == max { break; }
if buffer[p] == LIBRARY_BREAK as u8 {
buffer[p] = SHELF_BREAK as u8;
} else if buffer[p] == SHELF_BREAK as u8 {
buffer[p] = SERIES_BREAK as u8;
} else if buffer[p] == SERIES_BREAK as u8 {
buffer[p] = COLLECTION_BREAK as u8;
} else if buffer[p] == COLLECTION_BREAK as u8{
buffer[p] = VOLUME_BREAK as u8;
} else if buffer[p] == VOLUME_BREAK as u8 {
buffer[p] = BOOK_BREAK as u8;
} else if buffer[p] == BOOK_BREAK as u8 {
buffer[p] = CHAPTER_BREAK as u8;
} else if buffer[p] == CHAPTER_BREAK as u8 {
buffer[p] = SECTION_BREAK as u8;
} else if buffer[p] == SECTION_BREAK as u8 {
buffer[p] = SCROLL_BREAK as u8;
} else if buffer[p] == SCROLL_BREAK as u8 {
buffer[p] = LINE_BREAK as u8;
}
p += 1;
}
let temp: Vec<u8> = buffer.iter().cloned().collect();
let result: String = String::from_utf8(temp).expect("invalid utf8");
return result;
}
}
fn dephokenize(tokens: &mut Vec<PositionedScroll>) -> String {
let mut result: String = Default::default();
let mut coord = default_coordinate();
for ps in tokens {
if ps.scroll.len() > 0 {
result.push_str(&append_scroll(ps.clone(), coord));
}
coord = ps.coord;
}
return result;
}
fn append_scroll(token: PositionedScroll, mut coord: Coordinate) -> String {
let mut output: String = Default::default();
while coord < token.coord {
if coord.z.library < token.coord.z.library { output.push(LIBRARY_BREAK); coord.library_break(); continue; }
if coord.z.shelf < token.coord.z.shelf { output.push(SHELF_BREAK); coord.shelf_break(); continue; }
if coord.z.series < token.coord.z.series { output.push(SERIES_BREAK); coord.series_break(); continue; }
if coord.y.collection < token.coord.y.collection { output.push(COLLECTION_BREAK); coord.collection_break(); continue; }
if coord.y.volume < token.coord.y.volume { output.push(VOLUME_BREAK); coord.volume_break(); continue; }
if coord.y.book < token.coord.y.book { output.push(BOOK_BREAK); coord.book_break(); continue; }
if coord.x.chapter < token.coord.x.chapter { output.push(CHAPTER_BREAK); coord.chapter_break(); continue; }
if coord.x.section < token.coord.x.section { output.push(SECTION_BREAK); coord.section_break(); continue; }
if coord.x.scroll < token.coord.x.scroll { output.push(SCROLL_BREAK); coord.scroll_break(); continue; }
}
output.push_str(&token.scroll);
return output;
}
pub fn subtract(left: &str, right: &str) -> String {
let pl = phokenize(left);
let pr = phokenize(right);
let mut result: String = Default::default();
let mut pri = 0;
let max = pr.len();
let mut coord = default_coordinate();
for token in pl {
let mut do_append = pri == max;
if pri < max {
let compare = pr[pri].clone();
if token.coord < compare.coord {
do_append = true;
} else if token.coord == compare.coord {
pri += 1;
}
}
if do_append {
result.push_str(&append_scroll(token.clone(), coord));
coord = token.coord;
}
}
return result;
}
pub fn is_phext_break(byte: u8) -> bool {
return byte == LINE_BREAK as u8 ||
byte == SCROLL_BREAK as u8 ||
byte == SECTION_BREAK as u8 ||
byte == CHAPTER_BREAK as u8 ||
byte == BOOK_BREAK as u8 ||
byte == VOLUME_BREAK as u8 ||
byte == COLLECTION_BREAK as u8 ||
byte == SERIES_BREAK as u8 ||
byte == SHELF_BREAK as u8 ||
byte == LIBRARY_BREAK as u8;
}
pub fn normalize(phext: &str) -> String {
let mut arr = phokenize(phext);
return dephokenize(&mut arr);
}
pub fn default_coordinate() -> Coordinate {
let coord = Coordinate {
z: ZCoordinate {
library: 1,
shelf: 1,
series: 1
},
y: YCoordinate {
collection: 1,
volume: 1,
book: 1
},
x: XCoordinate {
chapter: 1,
section: 1,
scroll: 1
}
};
return coord;
}
pub fn to_coordinate(address: &str) -> Coordinate {
let mut result: Coordinate = default_coordinate();
let mut index: u8 = 0;
let mut value:u32 = 0;
let exp:u32 = 10;
for next in address.as_bytes() {
let byte = *next;
if byte == ADDRESS_MICRO_BREAK || byte == ADDRESS_MACRO_BREAK || byte == ADDRESS_MACRO_ALT {
match index {
1 => { result.z.library = value as usize; index += 1; },
2 => { result.z.shelf = value as usize; index += 1; },
3 => { result.z.series = value as usize; index += 1; },
4 => { result.y.collection = value as usize; index += 1; },
5 => { result.y.volume = value as usize; index += 1; },
6 => { result.y.book = value as usize; index += 1; },
7 => { result.x.chapter = value as usize; index += 1; },
8 => { result.x.section = value as usize; index += 1; },
_ => {}
}
value = 0;
}
if byte >= 0x30 && byte <= 0x39
{
value = exp * value + ((byte - 0x30) as u32);
if index == 0 { index = 1; }
}
}
if index > 0 {
result.x.scroll = value as usize;
}
return result;
}
fn validate_dimension_index(index: usize) -> bool {
return index >= COORDINATE_MINIMUM && index <= COORDINATE_MAXIMUM;
}
impl Coordinate {
pub fn validate_coordinate(&self) -> bool {
let ok = validate_dimension_index(self.z.library) &&
validate_dimension_index(self.z.shelf) &&
validate_dimension_index(self.z.series) &&
validate_dimension_index(self.y.collection) &&
validate_dimension_index(self.y.volume) &&
validate_dimension_index(self.y.book) &&
validate_dimension_index(self.x.chapter) &&
validate_dimension_index(self.x.section) &&
validate_dimension_index(self.x.scroll);
return ok;
}
pub fn to_string(&self) -> String {
if !self.validate_coordinate() {
return "".to_owned();
}
return format!("{}.{}.{}/{}.{}.{}/{}.{}.{}",
self.z.library, self.z.shelf, self.z.series,
self.y.collection, self.y.volume, self.y.book,
self.x.chapter, self.x.section, self.x.scroll);
}
pub fn to_urlencoded(&self) -> String {
if !self.validate_coordinate() {
return "".to_owned();
}
return format!("{}.{}.{};{}.{}.{};{}.{}.{}",
self.z.library, self.z.shelf, self.z.series,
self.y.collection, self.y.volume, self.y.book,
self.x.chapter, self.x.section, self.x.scroll);
}
fn advance_coordinate(index: usize) -> usize {
let next: usize = index + 1;
if next < COORDINATE_MAXIMUM {
return next;
}
return index; }
pub fn scroll_break(&mut self) {
self.x.scroll = Self::advance_coordinate(self.x.scroll);
}
pub fn section_break(&mut self) {
self.x.section = Self::advance_coordinate(self.x.section);
self.x.scroll = 1;
}
pub fn chapter_break(&mut self) {
self.x.chapter = Self::advance_coordinate(self.x.chapter);
self.x.section = 1;
self.x.scroll = 1;
}
pub fn book_break(&mut self) {
self.y.book = Self::advance_coordinate(self.y.book);
self.x.chapter = 1;
self.x.section = 1;
self.x.scroll = 1;
}
pub fn volume_break(&mut self) {
self.y.volume = Self::advance_coordinate(self.y.volume);
self.y.book = 1;
self.x.chapter = 1;
self.x.section = 1;
self.x.scroll = 1;
}
pub fn collection_break(&mut self) {
self.y.collection = Self::advance_coordinate(self.y.collection);
self.y.volume = 1;
self.y.book = 1;
self.x.chapter = 1;
self.x.section = 1;
self.x.scroll = 1;
}
pub fn series_break(&mut self) {
self.z.series = Self::advance_coordinate(self.z.series);
self.y.collection = 1;
self.y.volume = 1;
self.y.book = 1;
self.x.chapter = 1;
self.x.section = 1;
self.x.scroll = 1;
}
pub fn shelf_break(&mut self) {
self.z.shelf = Self::advance_coordinate(self.z.shelf);
self.z.series = 1;
self.y.collection = 1;
self.y.volume = 1;
self.y.book = 1;
self.x.chapter = 1;
self.x.section = 1;
self.x.scroll = 1;
}
pub fn library_break(&mut self) {
self.z.library = Self::advance_coordinate(self.z.library);
self.z.shelf = 1;
self.z.series = 1;
self.y.collection = 1;
self.y.volume = 1;
self.y.book = 1;
self.x.chapter = 1;
self.x.section = 1;
self.x.scroll = 1;
}
}