diff --git a/NEWS.md b/NEWS.md
index b2df7c977f..2f2a6af8e6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -65,3 +65,5 @@
* Documentation is now available also in *Dark* mode
([#2315](https://github.com/JuliaData/DataFrames.jl/pull/2315))
+* add rich display support for Markdown cell entries in HTML and LaTeX
+ ([#2346](https://github.com/JuliaData/DataFrames.jl/pull/2346))
diff --git a/Project.toml b/Project.toml
index 4d8bc8321c..80cb062e3c 100644
--- a/Project.toml
+++ b/Project.toml
@@ -10,6 +10,7 @@ Future = "9fa8497b-333b-5362-9e8d-4d0656e87820"
InvertedIndices = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Missings = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
diff --git a/src/DataFrames.jl b/src/DataFrames.jl
index f8ed23da02..9d45d575bb 100644
--- a/src/DataFrames.jl
+++ b/src/DataFrames.jl
@@ -6,6 +6,7 @@ using Reexport, SortingAlgorithms, Compat, Unicode, PooledArrays
using Base.Sort, Base.Order, Base.Iterators
using TableTraits, IteratorInterfaceExtensions
import LinearAlgebra: norm
+using Markdown
import DataAPI,
DataAPI.All,
diff --git a/src/abstractdataframe/io.jl b/src/abstractdataframe/io.jl
index 8c999fd8d7..eda9a60200 100644
--- a/src/abstractdataframe/io.jl
+++ b/src/abstractdataframe/io.jl
@@ -142,6 +142,10 @@ function _show(io::IO, ::MIME"text/html", df::AbstractDataFrame;
cell_val = df[row, column_name]
if ismissing(cell_val)
write(io, "
missing | ")
+ elseif cell_val isa Markdown.MD
+ write(io, "")
+ show(io, "text/html", cell_val)
+ write(io, " | ")
elseif cell_val isa SHOW_TABULAR_TYPES
write(io, "")
cell = sprint(ourshow, cell_val)
@@ -302,6 +306,8 @@ function _show(io::IO, ::MIME"text/latex", df::AbstractDataFrame;
cell = df[row,col]
if ismissing(cell)
print(io, "\\emph{missing}")
+ elseif cell isa Markdown.MD
+ print(io, strip(repr(MIME("text/latex"), cell)))
elseif cell isa SHOW_TABULAR_TYPES
print(io, "\\emph{")
print(io, latex_escape(sprint(ourshow, cell, context=io)))
@@ -410,17 +416,23 @@ function printtable(io::IO,
quotestr = string(quotemark)
for i in 1:n
for j in 1:p
- if ismissing(df[i, j])
+ cell = df[i, j]
+ if ismissing(cell)
print(io, missingstring)
- elseif isnothing(df[i, j])
+ elseif isnothing(cell)
print(io, nothingstring)
else
- if ! (etypes[j] <: Real)
+ if cell isa Markdown.MD
print(io, quotemark)
- escapedprint(io, df[i, j], quotestr)
+ r = repr(cell)
+ escapedprint(io, chomp(r), quotestr)
+ print(io, quotemark)
+ elseif ! (etypes[j] <: Real)
+ print(io, quotemark)
+ escapedprint(io, cell, quotestr)
print(io, quotemark)
else
- print(io, df[i, j])
+ print(io, cell)
end
end
if j < p
diff --git a/src/abstractdataframe/show.jl b/src/abstractdataframe/show.jl
index 85ba621c30..4bbc203943 100644
--- a/src/abstractdataframe/show.jl
+++ b/src/abstractdataframe/show.jl
@@ -54,6 +54,11 @@ ourshow(io::IO, x::Symbol) = ourshow(io, string(x))
ourshow(io::IO, x::Nothing; styled::Bool=false) = ourshow(io, "", styled=styled)
ourshow(io::IO, x::SHOW_TABULAR_TYPES; styled::Bool=false) =
ourshow(io, summary(x), styled=styled)
+function ourshow(io::IO, x::Markdown.MD)
+ r = repr(x)
+ len = min(length(r, 1, something(findfirst(==('\n'), r), lastindex(r)+1)-1), 32)
+ return print(io, len < length(r) - 1 ? first(r, len)*'…' : first(r, len))
+end
# AbstractChar: https://github.com/JuliaLang/julia/pull/34730 (1.5.0-DEV.261)
# Irrational: https://github.com/JuliaLang/julia/pull/34741 (1.5.0-DEV.266)
diff --git a/test/io.jl b/test/io.jl
index 0e9ea5bb64..6df042e064 100644
--- a/test/io.jl
+++ b/test/io.jl
@@ -1,28 +1,30 @@
module TestIO
-using Test, DataFrames, CategoricalArrays, Dates
+using Test, DataFrames, CategoricalArrays, Dates, Markdown
# Test LaTeX export
@testset "LaTeX export" begin
- df = DataFrame(A = 1:4,
+ df = DataFrame(A = Int64.( 1:4 ),
B = ["\$10.0", "M&F", "A~B", "\\alpha"],
C = ["A", "B", "C", "S"],
D = [1.0, 2.0, missing, 3.0],
E = CategoricalArray(["a", missing, "c", "d"]),
- F = Vector{String}(undef, 4)
+ F = Vector{String}(undef, 4),
+ G = [ md"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)", md"###A", md"``\frac{A}{B}``", md"*A*b**A**"]
)
str = """
- \\begin{tabular}{r|cccccc}
- \t& A & B & C & D & E & F\\\\
+ \\begin{tabular}{r|ccccccc}
+ \t& A & B & C & D & E & F & G\\\\
\t\\hline
- \t& $(Int) & String & String & Float64? & Cat…? & String\\\\
+ \t& Int64 & String & String & Float64? & Cat…? & String & MD…\\\\
\t\\hline
- \t1 & 1 & \\\$10.0 & A & 1.0 & a & \\emph{\\#undef} \\\\
- \t2 & 2 & M\\&F & B & 2.0 & \\emph{missing} & \\emph{\\#undef} \\\\
- \t3 & 3 & A\\textasciitilde{}B & C & \\emph{missing} & c & \\emph{\\#undef} \\\\
- \t4 & 4 & \\textbackslash{}\\textbackslash{}alpha & S & 3.0 & d & \\emph{\\#undef} \\\\
+ \t1 & 1 & \\\$10.0 & A & 1.0 & a & \\emph{\\#undef} & \\href{http://juliadata.github.io/DataFrames.jl}{DataFrames.jl} \\\\
+ \t2 & 2 & M\\&F & B & 2.0 & \\emph{missing} & \\emph{\\#undef} & \\#\\#\\#A \\\\
+ \t3 & 3 & A\\textasciitilde{}B & C & \\emph{missing} & c & \\emph{\\#undef} & \$\\frac{A}{B}\$ \\\\
+ \t4 & 4 & \\textbackslash{}\\textbackslash{}alpha & S & 3.0 & d & \\emph{\\#undef} & \\emph{A}b\\textbf{A} \\\\
\\end{tabular}
"""
+
@test repr(MIME("text/latex"), df) == str
@test repr(MIME("text/latex"), eachcol(df)) == str
@test repr(MIME("text/latex"), eachrow(df)) == str
@@ -130,6 +132,27 @@ end
@test_throws ArgumentError DataFrames._show(stdout, MIME("text/html"),
DataFrame(ones(2,2)), rowid=10)
+
+ df = DataFrame(
+ A=Int64[1,4,9,16],
+ B = [
+ md"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)",
+ md"###A",
+ md"``\frac{A}{B}``",
+ md"*A*b**A**" ]
+ )
+
+ @test repr(MIME("text/html"), df) ==
+ " | A | B |
---|
| " *
+ "Int64 | MD… |
---|
4 rows × 2 columns 1 | " *
+ "1 | |
---|
2 | 4 | |
---|
3 | 9 | |
---|
4 | " *
+ "16 | |
---|
"
+
end
# test limit attribute of IOContext is used
@@ -180,6 +203,101 @@ end
end
end
+@testset "Markdown as text/plain and as text/csv" begin
+ df = DataFrame(
+ A=Int64[1,4,9,16,25,36,49,64],
+ B = [
+ md"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)",
+ md"``\frac{x^2}{x^2+y^2}``",
+ md"# Header",
+ md"This is *very*, **very**, very, very, very, very, very, very, very long line" ,
+ md"",
+ Markdown.parse("∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫αγ∞1∫αγ∞2∫αγ∞3"),
+ Markdown.parse("∫αγ∞1∫αγ∞\n"*
+ " * 2∫αγ∞3∫αγ∞4\n"*
+ " * ∫αγ∞5∫αγ\n"*
+ " * ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0"),
+ Markdown.parse("∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫α\n"*
+ " * γ∞1∫α\n"*
+ " * γ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0"),
+ ]
+ )
+ @test sprint(show, "text/plain", df) == """
+ 8×2 DataFrame
+ │ Row │ A │ B │
+ │ │ Int64 │ Markdown.MD │
+ ├─────┼───────┼───────────────────────────────────┤
+ │ 1 │ 1 │ [DataFrames.jl](http://juliadata… │
+ │ 2 │ 4 │ \$\\frac{x^2}{x^2+y^2}\$ │
+ │ 3 │ 9 │ # Header │
+ │ 4 │ 16 │ This is *very*, **very**, very, … │
+ │ 5 │ 25 │ │
+ │ 6 │ 36 │ ∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫α… │
+ │ 7 │ 49 │ ∫αγ∞1∫αγ∞… │
+ │ 8 │ 64 │ ∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫α… │"""
+
+ @test sprint(show, "text/csv", df) ==
+ """
+ \"A\",\"B\"
+ 1,\"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)\"
+ 4,\"\$\\\\frac{x^2}{x^2+y^2}\$\"
+ 9,\"# Header\"
+ 16,\"This is *very*, **very**, very, very, very, very, very, very, very long line\"
+ 25,\"\"
+ 36,\"∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫αγ∞1∫αγ∞2∫αγ∞3\"
+ 49,\"∫αγ∞1∫αγ∞\\n\\n * 2∫αγ∞3∫αγ∞4\\n * ∫αγ∞5∫αγ\\n * ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0\"
+ 64,\"∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫α\\n\\n * γ∞1∫α\\n * γ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0\"
+ """
+end
+
+@testset "Markdown as HTML" begin
+ df = DataFrame(
+ A=Int64[1,4,9,16,25,36,49,64],
+ B = [
+ md"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)",
+ md"``\frac{x^2}{x^2+y^2}``",
+ md"# Header",
+ md"This is *very*, **very**, very, very, very, very, very, very, very long line" ,
+ md"",
+ Markdown.parse("∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0" *
+ "∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0"),
+ Markdown.parse("∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ\n"*
+ " * ∞7∫αγ\n"*
+ " * ∞8∫αγ\n"*
+ " * ∞9∫αγ∞0∫α\nγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0"),
+ Markdown.parse("∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫α\n"*
+ " * γ∞1∫α\n"*
+ " * γ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0"),
+ ]
+ )
+ @test sprint(show,"text/html",df) ==
+ "" *
+ " | A | B | " *
+ " | Int64 | MD… | " *
+ "" *
+ "" * "8 rows × 2 columns " *
+ "1 | 1 | | " *
+ "2 | 4 | | " *
+ "3 | 9 | Header\n | " *
+ "4 | 16 | " *
+ " This is very, very, very, very, very, very, very, very, very long line \n" *
+ " | " *
+ "5 | 25 | | " *
+ "6 | 36 | " *
+ " ∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0 \n" *
+ " | " *
+ "7 | 49 | " *
+ " ∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ \n \n∞7∫αγ \n \n∞8∫αγ \n \n∞9∫αγ∞0∫α \n \n \n γ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0 \n" *
+ " | " *
+ "8 | 64 | " *
+ " ∫αγ∞1∫αγ∞2∫αγ∞3∫αγ∞4∫αγ∞5∫αγ∞6∫αγ∞7∫αγ∞8∫αγ∞9∫αγ∞0∫α " *
+ "\n \n" * " |
---|
"
+end
+
@testset "empty data frame and DataFrameRow" begin
df = DataFrame(a = [1,2], b = [1.0, 2.0])
|