summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cell.rs95
-rw-r--r--src/lib.rs91
-rw-r--r--src/row.rs15
-rw-r--r--src/utils.rs38
4 files changed, 237 insertions, 2 deletions
diff --git a/src/cell.rs b/src/cell.rs
index 795c4c7..410ebd1 100644
--- a/src/cell.rs
+++ b/src/cell.rs
@@ -1,8 +1,7 @@
//! This module contains definition of table/row cells stuff
use super::format::Alignment;
-use super::utils::display_width;
-use super::utils::print_align;
+use super::utils::{display_width, print_align, HtmlEscape};
use super::{color, Attr, Terminal};
use std::io::{Error, Write};
use std::string::ToString;
@@ -244,6 +243,79 @@ impl Cell {
Err(e) => Err(term_error_to_io_error(e)),
}
}
+
+ /// Print the cell in HTML format to `out`.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
+ /// Convert the color to a hex value useful in CSS
+ fn color2hex(color: color::Color) -> &'static str {
+ match color {
+ color::BLACK => "#000000",
+ color::RED => "#aa0000",
+ color::GREEN => "#00aa00",
+ color::YELLOW => "#aa5500",
+ color::BLUE => "#0000aa",
+ color::MAGENTA => "#aa00aa",
+ color::CYAN => "#00aaaa",
+ color::WHITE => "#aaaaaa",
+ color::BRIGHT_BLACK => "#555555",
+ color::BRIGHT_RED => "#ff5555",
+ color::BRIGHT_GREEN => "#55ff55",
+ color::BRIGHT_YELLOW => "#ffff55",
+ color::BRIGHT_BLUE => "#5555ff",
+ color::BRIGHT_MAGENTA => "#ff55ff",
+ color::BRIGHT_CYAN => "#55ffff",
+ color::BRIGHT_WHITE => "#ffffff",
+
+ // Unknown colors, fallback to blakc
+ _ => "#000000",
+ }
+ };
+
+ let colspan = if self.hspan > 1 {
+ format!(" colspan=\"{}\"", self.hspan)
+ } else {
+ String::new()
+ };
+
+ // Process style properties like color
+ let mut styles = String::new();
+ for style in &self.style {
+ match style {
+ Attr::Bold => styles += "font-weight: bold;",
+ Attr::Italic(true) => styles += "font-style: italic;",
+ Attr::Underline(true) => styles += "text-decoration: underline;",
+ Attr::ForegroundColor(c) => {
+ styles += "color: ";
+ styles += color2hex(*c);
+ styles += ";";
+ }
+ Attr::BackgroundColor(c) => {
+ styles += "background-color: ";
+ styles += color2hex(*c);
+ styles += ";";
+ }
+ _ => {}
+ }
+ }
+ // Process alignment
+ match self.align {
+ Alignment::LEFT => styles += "text-align: left;",
+ Alignment::CENTER => styles += "text-align: center;",
+ Alignment::RIGHT => styles += "text-align: right;",
+ }
+
+ let content = self.content.join("<br />");
+ out.write_all(
+ format!(
+ "<td{1} style=\"{2}\">{0}</td>",
+ HtmlEscape(&content),
+ colspan,
+ styles
+ )
+ .as_bytes(),
+ )?;
+ Ok(self.hspan)
+ }
}
fn term_error_to_io_error(te: ::term::Error) -> Error {
@@ -361,6 +433,25 @@ mod tests {
}
#[test]
+ fn print_ascii_html() {
+ let ascii_cell = Cell::new("hello");
+ assert_eq!(ascii_cell.get_width(), 5);
+
+ let mut out = StringWriter::new();
+ let _ = ascii_cell.print_html(&mut out);
+ assert_eq!(out.as_string(), r#"<td style="text-align: left;">hello</td>"#);
+ }
+
+ #[test]
+ fn print_html_special_chars() {
+ let ascii_cell = Cell::new("<abc\">&'");
+
+ let mut out = StringWriter::new();
+ let _ = ascii_cell.print_html(&mut out);
+ assert_eq!(out.as_string(), r#"<td style="text-align: left;">&lt;abc&quot;&gt;&amp;&#39;</td>"#);
+ }
+
+ #[test]
fn align_left() {
let cell = Cell::new_align("test", Alignment::LEFT);
let mut out = StringWriter::new();
diff --git a/src/lib.rs b/src/lib.rs
index 66daaf9..1e02055 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -204,6 +204,28 @@ impl<'a> TableSlice<'a> {
pub fn printstd(&self) -> usize {
self.print_tty(false)
}
+
+ /// Print table in HTML format to `out`.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
+ // Compute column width
+ let column_num = self.get_column_num();
+ out.write_all(b"<table>")?;
+ // Print titles / table header
+ if let Some(ref t) = *self.titles {
+ out.write_all(b"<th>")?;
+ t.print_html(out, column_num)?;
+ out.write_all(b"</th>")?;
+ }
+ // Print rows
+ for r in self.rows {
+ out.write_all(b"<tr>")?;
+ r.print_html(out, column_num)?;
+ out.write_all(b"</tr>")?;
+ }
+ out.write_all(b"</table>")?;
+ out.flush()?;
+ Ok(())
+ }
}
impl<'a> IntoIterator for &'a TableSlice<'a> {
@@ -372,6 +394,10 @@ impl Table {
self.as_ref().printstd()
}
+ /// Print table in HTML format to `out`.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
+ self.as_ref().print_html(out)
+ }
}
impl Index<usize> for Table {
@@ -980,4 +1006,69 @@ mod tests {
assert_eq!(out, table.to_string().replace("\r\n","\n"));
assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
}
+
+ #[test]
+ fn table_html() {
+ let mut table = Table::new();
+ table.add_row(Row::new(vec![Cell::new("a"), Cell::new("bc"), Cell::new("def")]));
+ table.add_row(Row::new(vec![Cell::new("def"), Cell::new("bc"), Cell::new("a")]));
+ table.set_titles(Row::new(vec![Cell::new("t1"), Cell::new("t2"), Cell::new("t3")]));
+ let out = "\
+<table>\
+<th><td style=\"text-align: left;\">t1</td><td style=\"text-align: left;\">t2</td><td style=\"text-align: left;\">t3</td></th>\
+<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
+<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
+</table>";
+ let mut writer = StringWriter::new();
+ assert!(table.print_html(&mut writer).is_ok());
+ assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
+ table.unset_titles();
+ let out = "\
+<table>\
+<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
+<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
+</table>";
+ let mut writer = StringWriter::new();
+ assert!(table.print_html(&mut writer).is_ok());
+ assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
+ }
+
+ #[test]
+ fn table_html_colors() {
+ let mut table = Table::new();
+ table.add_row(Row::new(vec![
+ Cell::new("bold").style_spec("b"),
+ Cell::new("italic").style_spec("i"),
+ Cell::new("underline").style_spec("u"),
+ ]));
+ table.add_row(Row::new(vec![
+ Cell::new("left").style_spec("l"),
+ Cell::new("center").style_spec("c"),
+ Cell::new("right").style_spec("r"),
+ ]));
+ table.add_row(Row::new(vec![
+ Cell::new("red").style_spec("Fr"),
+ Cell::new("black").style_spec("Fd"),
+ Cell::new("yellow").style_spec("Fy"),
+ ]));
+ table.add_row(Row::new(vec![
+ Cell::new("bright magenta on cyan").style_spec("FMBc"),
+ Cell::new("white on bright green").style_spec("FwBG"),
+ Cell::new("default on blue").style_spec("Bb"),
+ ]));
+ table.set_titles(Row::new(vec![
+ Cell::new("span horizontal").style_spec("H3"),
+ ]));
+ let out = "\
+<table>\
+<th><td colspan=\"3\" style=\"text-align: left;\">span horizontal</td></th>\
+<tr><td style=\"font-weight: bold;text-align: left;\">bold</td><td style=\"font-style: italic;text-align: left;\">italic</td><td style=\"text-decoration: underline;text-align: left;\">underline</td></tr>\
+<tr><td style=\"text-align: left;\">left</td><td style=\"text-align: center;\">center</td><td style=\"text-align: right;\">right</td></tr>\
+<tr><td style=\"color: #aa0000;text-align: left;\">red</td><td style=\"color: #000000;text-align: left;\">black</td><td style=\"color: #aa5500;text-align: left;\">yellow</td></tr>\
+<tr><td style=\"color: #ff55ff;background-color: #00aaaa;text-align: left;\">bright magenta on cyan</td><td style=\"color: #aaaaaa;background-color: #55ff55;text-align: left;\">white on bright green</td><td style=\"background-color: #0000aa;text-align: left;\">default on blue</td></tr>\
+</table>";
+ let mut writer = StringWriter::new();
+ assert!(table.print_html(&mut writer).is_ok());
+ assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
+ }
}
diff --git a/src/row.rs b/src/row.rs
index 94c0d8e..dd3efcf 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -205,6 +205,21 @@ impl Row {
-> Result<usize, Error> {
self.__print(out, format, col_width, Cell::print_term)
}
+
+ /// Print the row in HTML format to `out`.
+ ///
+ /// If the row is has fewer columns than `col_num`, the row is padded with empty cells.
+ pub fn print_html<T: Write + ?Sized>(&self, out: &mut T, col_num: usize) -> Result<(), Error> {
+ let mut printed_columns = 0;
+ for cell in self.iter() {
+ printed_columns += cell.print_html(out)?;
+ }
+ // Pad with empty cells, if target width is not reached
+ for _ in 0..col_num - printed_columns {
+ Cell::default().print_html(out)?;
+ }
+ Ok(())
+ }
}
impl Default for Row {
diff --git a/src/utils.rs b/src/utils.rs
index f2d89ae..dbd8db1 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,4 +1,5 @@
//! Internal only utilities
+use std::fmt;
use std::io::{Error, ErrorKind, Write};
use std::str;
@@ -105,6 +106,43 @@ pub fn display_width(text: &str) -> usize {
width - hidden
}
+/// Wrapper struct which will emit the HTML-escaped version of the contained
+/// string when passed to a format string.
+pub struct HtmlEscape<'a>(pub &'a str);
+
+impl<'a> fmt::Display for HtmlEscape<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ // Because the internet is always right, turns out there's not that many
+ // characters to escape: http://stackoverflow.com/questions/7381974
+ let HtmlEscape(s) = *self;
+ let pile_o_bits = s;
+ let mut last = 0;
+ for (i, ch) in s.bytes().enumerate() {
+ match ch as char {
+ '<' | '>' | '&' | '\'' | '"' => {
+ fmt.write_str(&pile_o_bits[last.. i])?;
+ let s = match ch as char {
+ '>' => "&gt;",
+ '<' => "&lt;",
+ '&' => "&amp;",
+ '\'' => "&#39;",
+ '"' => "&quot;",
+ _ => unreachable!()
+ };
+ fmt.write_str(s)?;
+ last = i + 1;
+ }
+ _ => {}
+ }
+ }
+
+ if last < s.len() {
+ fmt.write_str(&pile_o_bits[last..])?;
+ }
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;