use std::borrow::Cow;
use std::collections::HashMap;
use oxilangtag::LanguageTag;
use serde::Deserialize;
use serde::Serialize;
use warc::BufferedBody;
use warc::Record;
use warc::WarcHeader;
use crate::common::Identification as IdentificationGen;
type Identification = IdentificationGen<String>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Metadata {
identification: Identification,
harmful_pp: Option<f32>,
tlsh: Option<String>,
quality_warnings: Option<Vec<String>>,
categories: Option<Vec<String>>,
sentence_identifications: Vec<Option<Identification>>,
}
impl Metadata {
pub fn new(
identification: &Identification,
sentence_identifications: &[Option<Identification>],
) -> Self {
Metadata {
identification: identification.clone(),
harmful_pp: None,
tlsh: None,
quality_warnings: None,
categories: None,
sentence_identifications: sentence_identifications.to_owned(),
}
}
pub fn add_annotation(&mut self, annotation: String) {
match &mut self.quality_warnings {
Some(anno) => anno.push(annotation),
None => self.quality_warnings = Some(vec![annotation]),
}
}
pub fn categories(&self) -> Option<&Vec<String>> {
self.categories.as_ref()
}
pub fn add_category(&mut self, category: String) {
match &mut self.categories {
Some(cat) => cat.push(category),
None => self.categories = Some(vec![category]),
}
}
pub fn set_categories(&mut self, categories: Option<Vec<String>>) {
self.categories = categories;
}
pub fn annotation(&self) -> Option<&Vec<String>> {
self.quality_warnings.as_ref()
}
pub fn sentence_identifications(&self) -> &[Option<Identification>] {
self.sentence_identifications.as_ref()
}
pub fn harmful_pp(&self) -> Option<f32> {
self.harmful_pp
}
pub fn set_harmful_pp(&mut self, harmful_pp: Option<f32>) {
self.harmful_pp = harmful_pp;
}
pub fn tlsh(&self) -> Option<&String> {
self.tlsh.as_ref()
}
pub fn set_tlsh(&mut self, tlsh: Option<String>) {
self.tlsh = tlsh;
}
pub fn set_sentence_identifications(
&mut self,
sentence_identifications: Vec<Option<Identification>>,
) {
self.sentence_identifications = sentence_identifications;
}
}
impl Default for Metadata {
fn default() -> Self {
Self {
identification: Identification::new(LanguageTag::parse("en".to_string()).unwrap(), 1.0),
harmful_pp: None,
tlsh: None,
quality_warnings: None,
categories: None,
sentence_identifications: vec![Some(Identification::new(
LanguageTag::parse("en".to_string()).unwrap(),
1.0,
))],
}
}
}
pub type WarcHeaders = HashMap<WarcHeader, Vec<u8>>;
pub type WarcHeadersSer = HashMap<WarcHeader, String>;
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(from = "DocumentSer", into = "DocumentSer")]
pub struct Document {
content: String,
warc_headers: WarcHeaders,
metadata: Metadata,
}
#[derive(Serialize, Deserialize)]
struct DocumentSer {
content: String,
warc_headers: WarcHeadersSer,
metadata: Metadata,
}
impl DocumentSer {
}
impl From<Document> for DocumentSer {
fn from(d: Document) -> Self {
let warc_headers = d
.warc_headers
.into_iter()
.map(|(k, v)| (k, String::from_utf8_lossy(&v).into_owned()))
.collect();
Self {
content: d.content,
warc_headers,
metadata: d.metadata,
}
}
}
impl From<DocumentSer> for Document {
fn from(d: DocumentSer) -> Self {
let warc_headers = d
.warc_headers
.into_iter()
.map(|(k, v)| (k, v.as_bytes().to_vec()))
.collect();
Self {
content: d.content,
warc_headers,
metadata: d.metadata,
}
}
}
impl Document {
pub fn new(content: String, warc_headers: WarcHeaders, metadata: Metadata) -> Self {
Self {
content,
warc_headers,
metadata,
}
}
pub fn from_record(record: Record<BufferedBody>, metadata: Metadata) -> Self {
let (header, body) = record.into_raw_parts();
let content = String::from_utf8_lossy(&body).into_owned();
let warc_headers = header.headers;
Self {
content,
warc_headers,
metadata,
}
}
pub fn identification(&self) -> &Identification {
&self.metadata.identification
}
pub fn content(&self) -> &String {
&self.content
}
pub fn warc_id(&self) -> Cow<str> {
String::from_utf8_lossy(self.warc_headers.get(&WarcHeader::RecordID).unwrap())
}
pub fn warc_headers(&self) -> &WarcHeaders {
&self.warc_headers
}
pub fn url(&self) -> Option<String> {
self.warc_headers()
.get(&warc::WarcHeader::TargetURI)
.map(|x| String::from_utf8_lossy(x).into_owned())
}
pub fn metadata_mut(&mut self) -> &mut Metadata {
&mut self.metadata
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn set_content(&mut self, content: String) {
self.content = content;
}
}
impl std::fmt::Debug for Document {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let headers_pp: HashMap<WarcHeader, String> = self
.warc_headers
.iter()
.map(|(k, v)| (k.clone(), String::from_utf8_lossy(v).to_string()))
.collect();
let lines = &self.content.lines().collect::<Vec<&str>>();
f.debug_struct("Document")
.field("content (as lines())", &lines)
.field("warc_headers", &headers_pp)
.field("metadata", &self.metadata)
.finish()
}
}
#[cfg(test)]
mod tests {
use warc::{Record, WarcHeader};
use super::{Document, Metadata};
#[test]
fn test_from_record() {
let record = Record::default();
let body = "foo
bar
baz";
let record = record.add_body(body);
let metadata = Metadata::default();
let doc = Document::from_record(record.clone(), metadata);
let (headers, body) = record.into_raw_parts();
assert_eq!(doc.content(), &String::from_utf8_lossy(&body).into_owned());
assert_eq!(doc.warc_headers(), &headers.headers);
assert_eq!(
doc.warc_id(),
String::from_utf8_lossy(headers.headers.get(&WarcHeader::RecordID).unwrap())
.into_owned()
);
}
#[test]
fn test_serialize() {
let m = Metadata::default();
let serialized = serde_json::to_string_pretty(&m).unwrap();
println!("{}", serialized);
let m2: Metadata = serde_json::from_str(&serialized).unwrap();
println!("{:?}", m2);
}
}