diff --git a/src/elements/link.rs b/src/elements/link.rs
index cac621c..d76db5d 100644
--- a/src/elements/link.rs
+++ b/src/elements/link.rs
@@ -216,7 +216,7 @@ impl RegexRule for LinkRule {
 #[cfg(test)]
 mod tests {
 	use crate::elements::style::Style;
-use crate::elements::text::Text;
+	use crate::elements::text::Text;
 	use crate::parser::langparser::LangParser;
 	use crate::parser::source::SourceFile;
 	use crate::validate_document;
diff --git a/src/elements/list.rs b/src/elements/list.rs
index 8a6397d..894fa33 100644
--- a/src/elements/list.rs
+++ b/src/elements/list.rs
@@ -1,235 +1,251 @@
-use std::{any::Any, cell::Ref, ops::Range, rc::Rc};
+use std::any::Any;
+use std::cell::Ref;
+use std::collections::HashMap;
+use std::ops::Range;
+use std::rc::Rc;
 
-use crate::{compiler::compiler::{Compiler, Target}, document::{document::{Document, DocumentAccessors}, element::{ElemKind, Element}}, parser::{parser::Parser, rule::Rule, source::{Cursor, Source, Token, VirtualSource}}};
-use ariadne::{Label, Report, ReportKind};
-use mlua::{Function, Lua};
+use crate::compiler::compiler::Compiler;
+use crate::compiler::compiler::Target;
+use crate::document::document::Document;
+use crate::document::document::DocumentAccessors;
+use crate::document::element::ContainerElement;
+use crate::document::element::ElemKind;
+use crate::document::element::Element;
+use crate::parser::parser::Parser;
+use crate::parser::rule::Rule;
+use crate::parser::source::Cursor;
+use crate::parser::source::Source;
+use crate::parser::source::Token;
+use crate::parser::source::VirtualSource;
+use crate::parser::util;
+use crate::parser::util::process_escaped;
+use crate::parser::util::Property;
+use crate::parser::util::PropertyMapError;
+use crate::parser::util::PropertyParser;
+use ariadne::Label;
+use ariadne::Report;
+use ariadne::ReportKind;
+use mlua::Function;
+use mlua::Lua;
+use regex::Match;
 use regex::Regex;
 
-use super::paragraph::Paragraph;
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum MarkerKind {
+	Open,
+	Close,
+}
+
+#[derive(Debug)]
+pub struct ListMarker {
+	pub(self) location: Token,
+	pub(self) numbered: bool,
+	pub(self) kind: MarkerKind,
+}
+
+impl Element for ListMarker {
+	fn location(&self) -> &Token { &self.location }
+
+	fn kind(&self) -> ElemKind { ElemKind::Block }
+
+	fn element_name(&self) -> &'static str { "List Marker" }
+
+	fn to_string(&self) -> String { format!("{self:#?}") }
+
+	fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
+		match compiler.target() {
+			Target::HTML => match (self.kind, self.numbered) {
+				(MarkerKind::Close, true) => Ok("</ol>".to_string()),
+				(MarkerKind::Close, false) => Ok("</ul>".to_string()),
+				(MarkerKind::Open, true) => Ok("<ol>".to_string()),
+				(MarkerKind::Open, false) => Ok("<ul>".to_string()),
+			},
+			_ => todo!(),
+		}
+	}
+}
 
 #[derive(Debug)]
 pub struct ListEntry {
-	location: Token,
-	numbering: Vec<(bool, usize)>,
-	content: Vec<Box<dyn Element>>,
-
-	// TODO bullet_maker : FnMut<...>
+	pub(self) location: Token,
+	pub(self) numbering: Vec<(bool, usize)>,
+	pub(self) content: Vec<Box<dyn Element>>,
+	pub(self) bullet: Option<String>,
 }
 
-impl ListEntry {
-	pub fn new(location: Token, numbering: Vec<(bool, usize)>, content: Vec<Box<dyn Element>>) -> Self {
-		Self { location, numbering, content }
-	}
-}
-
-#[derive(Debug)]
-pub struct List
-{
-	location: Token,
-	entries: Vec<ListEntry>
-}
-
-impl List
-{
-	pub fn new(location: Token) -> Self
-	{
-		Self
-		{
-			location,
-			entries: Vec::new()
-		}
-	}
-
-	pub fn push(&mut self, entry: ListEntry)
-	{
-		self.location.range = self.location.start()..entry.location.end();
-		self.entries.push(entry);
-	}
-}
-
-impl Element for List
-{
-    fn location(&self) -> &Token { &self.location }
-
-    fn kind(&self) -> ElemKind { ElemKind::Block }
-
-    fn element_name(&self) -> &'static str { "List" }
-
-    fn to_string(&self) -> String { format!("{self:#?}") }
-
-    fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
-		match compiler.target()
-		{
-			Target::HTML => {
-				let mut result = String::new();
-
-				//TODO: Do something about indexing
-				let mut current_list: Vec<bool> = vec![];
-				let mut match_stack = |result: &mut String, target: &Vec<(bool, usize)>| {
-
-					// Find index after which current_list and target differ
-					let mut match_idx = 0usize;
-					for i in 0..current_list.len()
-					{
-						if i >= target.len() || current_list[i] != target[i].0 { break }
-						else { match_idx = i+1; }
-					}
-
-					// Close until same match
-					for _ in match_idx..current_list.len()
-					{
-						result.push_str(["</ul>", "</ol>"][current_list.pop().unwrap() as usize]);
-					}
-
-					// Open
-					for i in match_idx..target.len()
-					{
-						result.push_str(["<ul>", "<ol>"][target[i].0 as usize]);
-						current_list.push(target[i].0);
-					}
-				};
-
-				match self.entries.iter()
-					.try_for_each(|ent|
-					{
-						match_stack(&mut result, &ent.numbering);
-						result.push_str("<li>");
-						match ent.content.iter().enumerate()
-							.try_for_each(|(_idx, elem)| {
-								match elem.compile(compiler, document) {
-									Err(e) => Err(e),
-									Ok(s) => { result.push_str(s.as_str()); Ok(()) }
-								}
-							})
-						{
-							Err(e) => Err(e),
-							_ => {
-								result.push_str("</li>");
-								Ok(())
-							}
-						}
-					})
-				{
-					Err(e) => return Err(e),
-					_ => {}
-				}
-				match_stack(&mut result, &Vec::<(bool, usize)>::new());
-
-				Ok(result)
-			}
-			Target::LATEX => Err("Unimplemented compiler".to_string())
-		}
-    }
-}
-
-/*
-impl Element for ListEntry
-{
+impl Element for ListEntry {
 	fn location(&self) -> &Token { &self.location }
-	fn kind(&self) -> ElemKind { ElemKind::Inline }
-	fn element_name(&self) -> &'static str { "List" }
+
+	fn kind(&self) -> ElemKind { ElemKind::Block }
+
+	fn element_name(&self) -> &'static str { "List Entry" }
+
 	fn to_string(&self) -> String { format!("{self:#?}") }
-	fn compile(&self, compiler: &Compiler) -> Result<String, String> {
-		lazy_static! {
-			static ref STATE_NAME : &'static str = "list.state";
-			static ref LIST_OPEN : [&'static str; 2] = ["<ul>", "<ol>"];
-			static ref LIST_CLOSE : [&'static str; 2] = ["</ul>", "</ol>"];
-		}
 
-		// TODO: State.shouldpreserve?
-		// Called upon every element
-		//let state = compiler.get_state_mut::<ListState, _>(*STATE_NAME)
-		//.or_else(|| {
-		//	compiler.insert_state(STATE_NAME.to_string(), Box::new(ListState(Vec::new())) as Box<dyn Any>);
-		//	compiler.get_state_mut::<ListState, _>(*STATE_NAME)
-		//}).unwrap();
-
-		match compiler.target()
-		{
+	fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
+		match compiler.target() {
 			Target::HTML => {
-				let mut result = String::new();
-
-				//TODO: Do something about indexing
-				//&self.numbering.iter()
-				//	.zip(&state.0)
-				//	.for_each(|((wants_numbered, _), is_numbered)|
-				//	{
-				//		
-				//	});
-
-				result.push_str("<li>");
-				match self.content.iter()
-					.try_for_each(|ent| match ent.compile(compiler) {
-						Err(e) => Err(e),
-						Ok(s) => Ok(result.push_str(s.as_str())),
-					})
-				{
-					Err(e) => return Err(e),
-					_ => {}
+				let mut result = "<li>".to_string();
+				for elem in &self.content {
+					result += elem.compile(compiler, document)?.as_str();
 				}
-				result.push_str("</li>");
-				//result.push_str(LIST_OPEN[self.numbered as usize]);
-				//self.entries.iter()
-				//	.for_each(|(_index, entry)|
-				//		result.push_str(format!("<li>{}</li>", compiler.compile(entry)).as_str()));
-				//result.push_str(LIST_CLOSE[self.numbered as usize]);
+				result += "</li>";
 				Ok(result)
 			}
-			Target::LATEX => Err("Unimplemented compiler".to_string())
+			_ => todo!(),
 		}
 	}
-}
-*/
 
-pub struct ListRule
-{
+	fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) }
+}
+
+impl ContainerElement for ListEntry {
+	fn contained(&self) -> &Vec<Box<dyn Element>> { &self.content }
+
+	fn push(&mut self, elem: Box<dyn Element>) -> Result<(), String> {
+		if elem.kind() == ElemKind::Block {
+			return Err("Cannot add block element inside a list".to_string());
+		}
+
+		self.content.push(elem);
+		Ok(())
+	}
+}
+
+pub struct ListRule {
 	start_re: Regex,
-	continue_re: Regex
+	continue_re: Regex,
+	properties: PropertyParser,
 }
 
 impl ListRule {
 	pub fn new() -> Self {
-		Self {
-			start_re: Regex::new(r"(?:^|\n)(?:[^\S\r\n]+)([*-]+).*").unwrap(),
-			continue_re: Regex::new(r"(?:^|\n)([^\S\r\n]+).*").unwrap(),
-		}
+		let mut props = HashMap::new();
+		props.insert(
+			"offset".to_string(),
+			Property::new(false, "Entry numbering offset".to_string(), None),
+		);
+		props.insert(
+			"bullet".to_string(),
+			Property::new(false, "Entry bullet".to_string(), None),
+		);
 
+		Self {
+			start_re: Regex::new(r"(?:^|\n)(?:[^\S\r\n]+)([*-]+)(?:\[((?:\\.|[^\\\\])*?)\])?(.*)")
+				.unwrap(),
+			continue_re: Regex::new(r"(?:^|\n)([^\S\r\n]+)([^\s].*)").unwrap(),
+			properties: PropertyParser { properties: props },
+		}
 	}
 
-	fn parse_depth(depth: &str, document: &dyn Document) -> Vec<(bool, usize)>
-	{
+	fn push_markers(
+		token: &Token,
+		parser: &dyn Parser,
+		document: &dyn Document,
+		current: &Vec<(bool, usize)>,
+		target: &Vec<(bool, usize)>,
+	) {
+		let mut start_pos = 0;
+		for i in 0..std::cmp::min(target.len(), current.len()) {
+			if current[i].0 != target[i].0 {
+				break;
+			}
+
+			start_pos += 1;
+		}
+
+		// Close
+		for i in start_pos..current.len() {
+			parser.push(
+				document,
+				Box::new(ListMarker {
+					location: token.clone(),
+					kind: MarkerKind::Close,
+					numbered: current[current.len() - 1 - (i - start_pos)].0,
+				}),
+			);
+		}
+
+		// Open
+		for i in start_pos..target.len() {
+			parser.push(
+				document,
+				Box::new(ListMarker {
+					location: token.clone(),
+					kind: MarkerKind::Open,
+					numbered: target[i].0,
+				}),
+			);
+		}
+	}
+
+	fn parse_properties(&self, m: Match) -> Result<(Option<usize>, Option<String>), String> {
+		let processed = process_escaped('\\', "]", m.as_str());
+		let pm = self.properties.parse(processed.as_str())?;
+
+		let offset = match pm.get("offset", |_, s| s.parse::<usize>()) {
+			Ok((prop, val)) => Some(val),
+			Err(err) => match err {
+				PropertyMapError::ParseError(err) => {
+					return Err(format!("Failed to parse `offset`: {err}"))
+				}
+				PropertyMapError::NotFoundError(err) => None,
+			},
+		};
+
+		let bullet = pm
+			.get("bullet", |_, s| -> Result<String, ()> { Ok(s.to_string()) })
+			.map(|(_, s)| s)
+			.ok();
+
+		Ok((offset, bullet))
+	}
+
+	fn parse_depth(depth: &str, document: &dyn Document, offset: usize) -> Vec<(bool, usize)> {
 		let mut parsed = vec![];
 		// FIXME: Previous iteration used to recursively retrieve the list indent
-		let prev_entry = document.last_element::<List>()
-			.and_then(|list| Ref::filter_map(list, |m| m.entries.last() ).ok() )
-			.and_then(|entry| Ref::filter_map(entry, |e| Some(&e.numbering)).ok() );
+		let prev_entry = document
+			.last_element::<ListEntry>()
+			.and_then(|entry| Ref::filter_map(entry, |e| Some(&e.numbering)).ok());
 
 		let mut continue_match = true;
-		depth.chars().enumerate().for_each(|(idx, c)|
-		{
-			let number = prev_entry.as_ref()
-				.and_then(|v| {
-					if !continue_match { return None }
-					let numbered = c == '-';
-
-					match v.get(idx)
-					{
-						None => None,
-						Some((prev_numbered, prev_idx)) => {
-							if *prev_numbered != numbered { continue_match = false; None } // New depth
-							else if idx+1 == v.len() { Some(prev_idx+1) } // Increase from previous
-							else { Some(*prev_idx) } // Do nothing
+		depth.chars().enumerate().for_each(|(idx, c)| {
+			let number = if offset == 0 {
+				prev_entry
+					.as_ref()
+					.and_then(|v| {
+						if !continue_match {
+							return None;
 						}
-					}
-				})
-				.or(Some(0usize))
-				.unwrap();
+						let numbered = c == '-';
 
-			match c
-			{
+						match v.get(idx) {
+							None => None,
+							Some((prev_numbered, prev_idx)) => {
+								if *prev_numbered != numbered {
+									continue_match = false;
+									None
+								}
+								// New depth
+								else if idx + 1 == v.len() {
+									Some(prev_idx + 1)
+								}
+								// Increase from previous
+								else {
+									Some(*prev_idx)
+								} // Do nothing
+							}
+						}
+					})
+					.unwrap_or(1)
+			} else {
+				offset
+			};
+
+			match c {
 				'*' => parsed.push((false, number)),
 				'-' => parsed.push((true, number)),
-				_ => panic!("Unimplemented")
+				_ => panic!("Unimplemented"),
 			}
 		});
 
@@ -237,101 +253,186 @@ impl ListRule {
 	}
 }
 
-impl Rule for ListRule
-{
+impl Rule for ListRule {
 	fn name(&self) -> &'static str { "List" }
 
 	fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
-		self.start_re.find_at(cursor.source.content(), cursor.pos)
-			.map_or(None,
-			|m| Some((m.start(), Box::new([false;0]) as Box<dyn Any>)) )
+		self.start_re
+			.find_at(cursor.source.content(), cursor.pos)
+			.map_or(None, |m| {
+				Some((m.start(), Box::new([false; 0]) as Box<dyn Any>))
+			})
 	}
 
-	fn on_match<'a>(&self, parser: &dyn Parser, document: &'a dyn Document<'a>, cursor: Cursor, _match_data: Option<Box<dyn Any>>)
-		-> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	fn on_match<'a>(
+		&self,
+		parser: &dyn Parser,
+		document: &'a dyn Document<'a>,
+		cursor: Cursor,
+		_match_data: Option<Box<dyn Any>>,
+	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
 		let mut reports = vec![];
+
 		let content = cursor.source.content();
-		let (end_cursor, numbering, source) = match self.start_re.captures_at(content, cursor.pos) {
-			None => panic!("Unknown error"),
-			Some(caps) => {
-				let mut end_pos = caps.get(0).unwrap().end();
+		let mut end_cursor = cursor.clone();
+		loop {
+			if let Some(captures) = self.start_re.captures_at(content, end_cursor.pos) {
+				if captures.get(0).unwrap().start() != end_cursor.pos {
+					break;
+				}
+				// Advance cursor
+				end_cursor = end_cursor.at(captures.get(0).unwrap().end());
 
-				let mut spacing = None; // Spacing used to continue list entry
-				loop {
-					// If another entry starts on the next line, don't continue matching
-					match self.next_match(&cursor.at(end_pos))
-					{
-						Some((pos, _)) => {
-							if pos == end_pos { break }
-						}
-						None => {},
-					}
-
-					// Continue matching as current entry
-					match self.continue_re.captures_at(content, end_pos) {
-						None => break,
-						Some(continue_caps) => {
-							if continue_caps.get(0).unwrap().start() != end_pos { break }
-
-							// Get the spacing
-							let cap_spacing = continue_caps.get(1).unwrap();
-							match &spacing {
-								None => spacing = Some(cap_spacing.range()),
-								Some(spacing) => 'some: {
-									if content[cap_spacing.range()] == content[spacing.clone()] { break 'some }
-
-									reports.push(
-										Report::build(ReportKind::Warning, cursor.source.clone(), continue_caps.get(1).unwrap().start())
-										.with_message("Invalid list entry spacing")
-										.with_label(
-											Label::new((cursor.source.clone(), cap_spacing.range()))
-											.with_message("Spacing for list entries must match")
-											.with_color(parser.colors().warning))
-										.with_label(
-											Label::new((cursor.source.clone(), spacing.clone()))
-											.with_message("Previous spacing")
-											.with_color(parser.colors().warning))
-										.finish());
-								},
-							}
-							end_pos = continue_caps.get(0).unwrap().end();
+				// Properties
+				let mut offset = None;
+				let mut bullet = None;
+				if let Some(properties) = captures.get(2) {
+					match self.parse_properties(properties) {
+						Err(err) => {
+							reports.push(
+								Report::build(
+									ReportKind::Warning,
+									cursor.source.clone(),
+									properties.start(),
+								)
+								.with_message("Invalid List Entry Properties")
+								.with_label(
+									Label::new((cursor.source.clone(), properties.range()))
+										.with_message(err)
+										.with_color(parser.colors().warning),
+								)
+								.finish(),
+							);
+							break;
 						}
+						Ok(props) => (offset, bullet) = props,
 					}
 				}
+				// Get bullet from previous entry if it exists
+				if bullet.is_none() {
+					bullet = document
+						.last_element::<ListEntry>()
+						.and_then(|prev| prev.bullet.clone())
+				}
 
-				let start_pos = caps.get(1).unwrap().end();
-				let source = VirtualSource::new(
-					Token::new(start_pos..end_pos, cursor.source.clone()),
-					"List Entry".to_string(),
-					content.as_str()[start_pos..end_pos].to_string(),
+				// Depth
+				let depth = ListRule::parse_depth(
+					captures.get(1).unwrap().as_str(),
+					document,
+					offset.unwrap_or(0),
 				);
 
-				(cursor.at(end_pos),
-				ListRule::parse_depth(caps.get(1).unwrap().as_str(), document),
-				source)
-			},
-		};
+				// Content
+				let entry_start = captures.get(0).unwrap().start();
+				let mut entry_content = captures.get(3).unwrap().as_str().to_string();
+				let mut spacing: Option<(Range<usize>, &str)> = None;
+				while let Some(captures) = self.continue_re.captures_at(content, end_cursor.pos) {
+					// Break if next element is another entry
+					if captures.get(0).unwrap().start() != end_cursor.pos
+						|| captures
+							.get(2)
+							.unwrap()
+							.as_str()
+							.find(|c| c == '*' || c == '-')
+							== Some(0)
+					{
+						break;
+					}
+					// Advance cursor
+					end_cursor = end_cursor.at(captures.get(0).unwrap().end());
 
-        let parsed_entry = parser.parse(Rc::new(source), Some(document));
-		let mut parsed_paragraph = parsed_entry.last_element_mut::<Paragraph>().unwrap(); // Extract content from paragraph
-		let entry = ListEntry::new(
-			Token::new(cursor.pos..end_cursor.pos, cursor.source.clone()),
-			numbering,
-			std::mem::replace(&mut parsed_paragraph.content, Vec::new())
-		);
+					// Spacing
+					let current_spacing = captures.get(1).unwrap().as_str();
+					if let Some(spacing) = &spacing {
+						if spacing.1 != current_spacing {
+							reports.push(
+								Report::build(
+									ReportKind::Warning,
+									cursor.source.clone(),
+									captures.get(1).unwrap().start(),
+								)
+								.with_message("Invalid list entry spacing")
+								.with_label(
+									Label::new((
+										cursor.source.clone(),
+										captures.get(1).unwrap().range(),
+									))
+									.with_message("Spacing for list entries do not match")
+									.with_color(parser.colors().warning),
+								)
+								.with_label(
+									Label::new((cursor.source.clone(), spacing.0.clone()))
+										.with_message("Previous spacing")
+										.with_color(parser.colors().warning),
+								)
+								.finish(),
+							);
+						}
+					} else {
+						spacing = Some((captures.get(1).unwrap().range(), current_spacing));
+					}
 
-		// Ger previous list, if none insert a new list
-		let mut list = match document.last_element_mut::<List>()
-		{
-			Some(last) => last,
-			None => {
-				parser.push(document,
-					Box::new(List::new(
-							Token::new(cursor.pos..end_cursor.pos, cursor.source.clone()))));
-				document.last_element_mut::<List>().unwrap()
+					entry_content += " ";
+					entry_content += captures.get(2).unwrap().as_str();
+				}
+
+				// Parse entry content
+				let token = Token::new(end_cursor.pos..end_cursor.pos, end_cursor.source.clone());
+				let entry_src = Rc::new(VirtualSource::new(
+					token.clone(),
+					"List Entry".to_string(),
+					entry_content,
+				));
+				let parsed_content = match util::parse_paragraph(parser, entry_src, document) {
+					Err(err) => {
+						reports.push(
+							Report::build(ReportKind::Warning, token.source(), token.range.start)
+								.with_message("Unable to Parse List Entry")
+								.with_label(
+									Label::new((token.source(), token.range.clone()))
+										.with_message(err)
+										.with_color(parser.colors().warning),
+								)
+								.finish(),
+						);
+						break;
+					}
+					Ok(mut paragraph) => std::mem::replace(&mut paragraph.content, vec![]),
+				};
+
+				if let Some(previous_depth) = document
+					.last_element::<ListEntry>()
+					.map(|ent| ent.numbering.clone())
+				{
+					ListRule::push_markers(&token, parser, document, &previous_depth, &depth);
+				} else {
+					ListRule::push_markers(&token, parser, document, &vec![], &depth);
+				}
+
+				parser.push(
+					document,
+					Box::new(ListEntry {
+						location: Token::new(
+							entry_start..end_cursor.pos,
+							end_cursor.source.clone(),
+						),
+						numbering: depth,
+						content: parsed_content,
+						bullet,
+					}),
+				);
+			} else {
+				break;
 			}
-		};
-		list.push(entry);
+		}
+
+		// Close all lists
+		let current = document
+			.last_element::<ListEntry>()
+			.map(|ent| ent.numbering.clone())
+			.unwrap();
+		let token = Token::new(end_cursor.pos..end_cursor.pos, end_cursor.source.clone());
+		ListRule::push_markers(&token, parser, document, &current, &Vec::new());
 
 		(end_cursor, reports)
 	}
@@ -339,3 +440,78 @@ impl Rule for ListRule
 	// TODO
 	fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
 }
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use crate::compiler::compiler::Target;
+	use crate::elements::paragraph::Paragraph;
+	use crate::elements::text::Text;
+	use crate::parser::langparser::LangParser;
+	use crate::parser::source::SourceFile;
+	use crate::validate_document;
+
+	#[test]
+	fn parser() {
+		let source = Rc::new(SourceFile::with_content(
+			"".to_string(),
+			r#"
+ * 1
+ *[offset=7] 2
+	continued
+ * 3
+
+ * New list
+ *-[bullet=(*)] A
+ *- B
+ * Back
+ *-* More nested
+"#
+			.to_string(),
+			None,
+		));
+		let parser = LangParser::default();
+		let compiler = Compiler::new(Target::HTML, None);
+		let doc = parser.parse(source, None);
+
+		validate_document!(doc.content().borrow(), 0,
+			ListMarker { numbered == false, kind == MarkerKind::Open };
+			ListEntry { numbering == vec![(false, 1)] } {
+				Text { content == "1" };
+			};
+			ListEntry { numbering == vec![(false, 7)] } {
+				Text { /*content == "2 continued"*/ };
+			};
+			ListEntry { numbering == vec![(false, 8)] } {
+				Text { content == "3" };
+			};
+			ListMarker { numbered == false, kind == MarkerKind::Close };
+
+			Paragraph;
+
+			ListMarker { numbered == false, kind == MarkerKind::Open };
+			ListEntry { numbering == vec![(false, 1)] } {
+				Text { content == "New list" };
+			};
+			ListMarker { numbered == true, kind == MarkerKind::Open };
+				ListEntry { numbering == vec![(false, 2), (true, 1)], bullet == Some("(*)".to_string()) } {
+					Text { content == "A" };
+				};
+				ListEntry { numbering == vec![(false, 2), (true, 2)], bullet == Some("(*)".to_string()) } {
+					Text { content == "B" };
+				};
+			ListMarker { numbered == true, kind == MarkerKind::Close };
+			ListEntry { numbering == vec![(false, 2)] } {
+				Text { content == "Back" };
+			};
+			ListMarker { numbered == true, kind == MarkerKind::Open };
+			ListMarker { numbered == false, kind == MarkerKind::Open };
+			ListEntry { numbering == vec![(false, 3), (true, 1), (false, 1)] } {
+				Text { content == "More nested" };
+			};
+			ListMarker { numbered == false, kind == MarkerKind::Close };
+			ListMarker { numbered == true, kind == MarkerKind::Close };
+			ListMarker { numbered == false, kind == MarkerKind::Close };
+		);
+	}
+}
diff --git a/src/elements/raw.rs b/src/elements/raw.rs
index d6f95d0..7a07903 100644
--- a/src/elements/raw.rs
+++ b/src/elements/raw.rs
@@ -67,7 +67,7 @@ impl RawRule {
 				Regex::new(r"\{\?(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)(\?\}))?")
 					.unwrap(),
 			],
-			properties: PropertyParser{ properties: props },
+			properties: PropertyParser { properties: props },
 		}
 	}
 }
@@ -268,15 +268,18 @@ impl RegexRule for RawRule {
 mod tests {
 	use super::*;
 	use crate::compiler::compiler::Target;
+	use crate::elements::paragraph::Paragraph;
+	use crate::elements::text::Text;
 	use crate::parser::langparser::LangParser;
 	use crate::parser::source::SourceFile;
+	use crate::validate_document;
 
 	#[test]
-	fn raw_tests() {
+	fn parser() {
 		let source = Rc::new(SourceFile::with_content(
 			"".to_string(),
 			r#"
-Break{?[kind=block]<RAW>?}NewParagraph
+Break{?[kind=block] Raw?}NewParagraph{?<b>?}
 				"#
 			.to_string(),
 			None,
@@ -285,13 +288,13 @@ Break{?[kind=block]<RAW>?}NewParagraph
 		let compiler = Compiler::new(Target::HTML, None);
 		let doc = parser.parse(source, None);
 
-		let borrow = doc.content().borrow();
-		let found = borrow
-			.iter()
-			.filter_map(|e| e.downcast_ref::<Raw>())
-			.collect::<Vec<_>>();
-
-		assert_eq!(found[0].compile(&compiler, &*doc), Ok("<RAW>".to_string()));
-		//assert_eq!(found[1].compile(&compiler, &*doc), Ok("<RAW>".to_string()));
+		validate_document!(doc.content().borrow(), 0,
+			Paragraph;
+			Raw { kind == ElemKind::Block, content == "Raw" };
+			Paragraph {
+				Text;
+				Raw { kind == ElemKind::Inline, content == "<b>" };
+			};
+		);
 	}
 }