#![forbid(unsafe_code)]
extern crate quick_xml;
pub mod schema;
use quick_xml::{NsReader, events::Event, name::ResolveResult};
use schema::Namespace;
use std::io::BufRead;
enum PageChildElement {
Ns,
Revision,
Title,
Unknown,
}
enum RevisionChildElement {
Format,
Model,
Text,
Unknown,
}
#[derive(Debug)]
pub enum Error {
Format(usize),
NotSupported(usize),
XmlReader(quick_xml::Error),
}
#[derive(Debug)]
pub struct Page {
pub format: Option<String>,
pub model: Option<String>,
pub namespace: Namespace,
pub text: String,
pub title: String,
}
pub struct Parser<R: BufRead> {
buffer: Vec<u8>,
reader: NsReader<R>,
started: bool,
}
impl std::fmt::Display for Error {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::Format(position) => write!(formatter, "Invalid format at position {}", position),
Error::NotSupported(position) => write!(
formatter,
"The element at position {} is not supported",
position
),
Error::XmlReader(error) => error.fmt(formatter),
}
}
}
impl From<quick_xml::Error> for Error {
fn from(value: quick_xml::Error) -> Self {
Error::XmlReader(value)
}
}
impl<R: BufRead> Iterator for Parser<R> {
type Item = Result<Page, Error>;
fn next(&mut self) -> Option<Self::Item> {
Some(match next(self) {
Err(error) => Err(error),
Ok(item) => Ok(item?),
})
}
}
fn match_namespace(namespace: ResolveResult<'_>) -> bool {
matches!(
namespace,
ResolveResult::Bound(quick_xml::name::Namespace(
b"http://www.mediawiki.org/xml/export-0.10/"
| b"http://www.mediawiki.org/xml/export-0.11/",
))
)
}
fn next(parser: &mut Parser<impl BufRead>) -> Result<Option<Page>, Error> {
if !parser.started {
loop {
parser.buffer.clear();
if let (namespace, Event::Start(event)) =
parser.reader.read_resolved_event_into(&mut parser.buffer)?
{
if match_namespace(namespace) && event.local_name().as_ref() == b"mediawiki" {
break;
}
return Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
));
}
}
parser.started = true;
}
loop {
parser.buffer.clear();
if !match parser
.reader
.read_resolved_event_into(&mut parser.buffer)?
{
(_, Event::End(_)) => return Ok(None),
(namespace, Event::Start(event)) => {
match_namespace(namespace) && event.local_name().as_ref() == b"page"
}
_ => continue,
} {
skip_element(parser)?;
continue;
}
let mut format = None;
let mut model = None;
let mut namespace = None;
let mut text = None;
let mut title = None;
loop {
parser.buffer.clear();
match match parser.reader.read_resolved_event_into(&mut parser.buffer)? {
(_, Event::End(_)) => {
return match (namespace, text, title) {
(Some(namespace), Some(text), Some(title)) => Ok(Some(Page {
format,
model,
namespace,
text,
title,
})),
_ => Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
)),
};
}
(namespace, Event::Start(event)) => {
if match_namespace(namespace) {
match event.local_name().as_ref() {
b"ns" => PageChildElement::Ns,
b"revision" => PageChildElement::Revision,
b"title" => PageChildElement::Title,
_ => PageChildElement::Unknown,
}
} else {
PageChildElement::Unknown
}
}
_ => continue,
} {
PageChildElement::Ns => match parse_text(parser, &namespace)?.parse::<i32>() {
Err(_) => {
return Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
));
}
Ok(value) => {
namespace = Some(Namespace::from(value));
continue;
}
},
PageChildElement::Revision => {
if text.is_some() {
return Err(Error::NotSupported(
parser.reader.buffer_position().try_into().unwrap(),
));
}
loop {
parser.buffer.clear();
match match parser.reader.read_resolved_event_into(&mut parser.buffer)? {
(_, Event::End(_)) => match text {
None => {
return Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
));
}
Some(_) => break,
},
(namespace, Event::Start(event)) => {
if match_namespace(namespace) {
match event.local_name().as_ref() {
b"format" => RevisionChildElement::Format,
b"model" => RevisionChildElement::Model,
b"text" => RevisionChildElement::Text,
_ => RevisionChildElement::Unknown,
}
} else {
RevisionChildElement::Unknown
}
}
_ => continue,
} {
RevisionChildElement::Format => {
format = Some(parse_text(parser, &format)?)
}
RevisionChildElement::Model => {
model = Some(parse_text(parser, &model)?)
}
RevisionChildElement::Text => text = Some(parse_text(parser, &text)?),
RevisionChildElement::Unknown => skip_element(parser)?,
}
}
continue;
}
PageChildElement::Title => {
title = Some(parse_text(parser, &title)?);
continue;
}
PageChildElement::Unknown => skip_element(parser)?,
}
}
}
}
pub fn parse<R: BufRead>(source: R) -> Parser<R> {
let mut reader = NsReader::from_reader(source);
reader.config_mut().expand_empty_elements = true;
Parser {
buffer: vec![],
reader,
started: false,
}
}
fn parse_text(
parser: &mut Parser<impl BufRead>,
output: &Option<impl Sized>,
) -> Result<String, Error> {
if output.is_some() {
return Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
));
}
parser.buffer.clear();
let text = match parser
.reader
.read_resolved_event_into(&mut parser.buffer)?
.1
{
Event::Text(text) => text.unescape().unwrap().to_string(),
Event::End { .. } => return Ok(String::new()),
_ => {
return Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
));
}
};
parser.buffer.clear();
if let Event::End(_) = parser
.reader
.read_resolved_event_into(&mut parser.buffer)?
.1
{
Ok(text)
} else {
Err(Error::Format(
parser.reader.buffer_position().try_into().unwrap(),
))
}
}
fn skip_element(parser: &mut Parser<impl BufRead>) -> Result<(), quick_xml::Error> {
let mut level = 0;
loop {
parser.buffer.clear();
match parser
.reader
.read_resolved_event_into(&mut parser.buffer)?
.1
{
Event::End(_) => {
if level == 0 {
return Ok(());
}
level -= 1;
}
Event::Start(_) => level += 1,
_ => {}
}
}
}