diff --git a/docs/src/index.md b/docs/src/index.md index 67ffeb6..3a09a19 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -49,6 +49,10 @@ my_table = Table(df; offset_x=60, offset_y=80, size_x=150, size_y=40) push!(s4, my_table) push!(pres, s4) +# and what about a nice link in slide 2 to the table-slide +text = TextBox(; content="Click here to see a nice table", offset_x=100, offset_y=140, size_x=150, size_y=20, hlink = s4) +push!(s2, text) + pres # output diff --git a/src/AbstractShape.jl b/src/AbstractShape.jl index 742dd1c..a689033 100644 --- a/src/AbstractShape.jl +++ b/src/AbstractShape.jl @@ -21,4 +21,7 @@ end function _show_string(shape::AbstractShape, compact::Bool) return "$(typeof(shape))" -end \ No newline at end of file +end + +hlink_xml(hlink) = Dict("a:hlinkClick" => Dict("rId" => "rId$(rid(hlink))", "action" => "ppaction://hlinksldjump")) +has_hyperlink(s::AbstractShape) = hasfield(typeof(s), :hlink) && !isnothing(s.hlink) \ No newline at end of file diff --git a/src/Presentation.jl b/src/Presentation.jl index 6e19030..e138e46 100644 --- a/src/Presentation.jl +++ b/src/Presentation.jl @@ -41,7 +41,7 @@ struct Presentation function Presentation(slides::Vector{Slide}, author::String, title::String) pres = new(title, author, Slide[], PresentationState()) if isempty(slides) - slides = [Slide(; title=title, layout=TITLE_SLIDE_LAYOUT)] + slides = [Slide(; title=title, layout=TITLE_SLIDE_LAYOUT, slide_nr = 1)] end for slide in slides push!(pres, slide) @@ -68,6 +68,7 @@ end function Base.push!(pres::Presentation, slide::Slide) slide.rid = new_rid(pres) + slide.slide_nr = length(slides(pres)) + 1 return push!(slides(pres), slide) end @@ -184,4 +185,12 @@ function update_presentation_state!(p::Presentation, template::ZipBufferReader) sz = PresentationSize(parse(Int, cx), parse(Int, cy)) p._state.size = sz return nothing +end + +# the slide filename (e.g. slide2.xml) can be used in the slide relationships to enable hyperlinks for example +# We need to make sure that slide N is also really written to slideN.xml therefore slide numbers are updated just before writing +function update_slide_nrs!(p::Presentation) + for (index, slide) in enumerate(slides(p)) + slide.slide_nr = index + end end \ No newline at end of file diff --git a/src/Slide.jl b/src/Slide.jl index d690284..917480e 100644 --- a/src/Slide.jl +++ b/src/Slide.jl @@ -42,13 +42,15 @@ mutable struct Slide shapes::Vector{AbstractShape} rid::Int layout::Int + slide_nr::Int function Slide( title::String, shapes::Vector{<:AbstractShape}, rid::Int=0, layout::Int=DEFAULT_SLIDE_LAYOUT, + slide_nr::Int=0 ) - slide = new(title, AbstractShape[], rid, layout) + slide = new(title, AbstractShape[], rid, layout, slide_nr) for shape in shapes push!(slide, shape) end @@ -57,7 +59,7 @@ mutable struct Slide end shapes(s::Slide) = s.shapes rid(s::Slide) = s.rid - +slide_nr(s::Slide) = s.slide_nr Slide(shapes::Vector{<:AbstractShape}; kwargs...) = Slide(; shapes=shapes, kwargs...) function Slide(; @@ -65,8 +67,9 @@ function Slide(; shapes::Vector{<:AbstractShape}=AbstractShape[], rid::Int=0, layout::Int=DEFAULT_SLIDE_LAYOUT, + slide_nr::Int=0 ) - return Slide(title, shapes, rid, layout) + return Slide(title, shapes, rid, layout, slide_nr) end function new_rid(slide::Slide) @@ -77,6 +80,8 @@ function new_rid(slide::Slide) end end +slide_fname(s::Slide) = "slide$(s.slide_nr).xml" + function Base.push!(slide::Slide, shape::AbstractShape) if has_rid(shape) push!(shapes(slide), set_rid(shape, new_rid(slide))) @@ -138,6 +143,20 @@ function init_sptree()::AbstractDict ) end +function type_schema(s::Slide) + return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" +end + +function relationship_xml(s::Slide) + return Dict( + "Relationship" => [ + Dict("Id" => "rId$(rid(s))"), + Dict("Type" => type_schema(s)), + Dict("Target" => slide_fname(s)), + ], + ) +end + function make_slide_relationships(s::Slide)::AbstractDict xml_slide_rels = OrderedDict("Relationships" => Dict[]) push!( @@ -160,6 +179,9 @@ function make_slide_relationships(s::Slide)::AbstractDict if has_rid(shape) push!(xml_slide_rels["Relationships"], relationship_xml(shape)) end + if has_hyperlink(shape) + push!(xml_slide_rels["Relationships"], relationship_xml(shape.hlink)) + end end return xml_slide_rels end \ No newline at end of file diff --git a/src/TextBox.jl b/src/TextBox.jl index 374cb04..cf44c2e 100644 --- a/src/TextBox.jl +++ b/src/TextBox.jl @@ -63,6 +63,7 @@ struct TextBox <: AbstractShape offset_y::Int # EMUs size_x::Int # EMUs size_y::Int # EMUs + hlink::Union{Nothing, Any} function TextBox( content::AbstractString, offset_x::Real, # millimeters @@ -70,6 +71,7 @@ struct TextBox <: AbstractShape size_x::Real, # millimeters size_y::Real, # millimeters style::Dict=Dict("bold" => false, "italic" => false), + hlink::Union{Nothing, Any} = nothing ) # input is in mm return new( @@ -78,6 +80,7 @@ struct TextBox <: AbstractShape Int(round(offset_y * _EMUS_PER_MM)), Int(round(size_x * _EMUS_PER_MM)), Int(round(size_y * _EMUS_PER_MM)), + hlink ) end end @@ -90,6 +93,7 @@ function TextBox(; size_x::Real=40, # millimeters size_y::Real=30, # millimeters style::Dict=Dict("bold" => false, "italic" => false), + hlink::Union{Nothing, Any} = nothing ) return TextBox( content, @@ -98,6 +102,7 @@ function TextBox(; size_x, size_y, style, + hlink ) end @@ -130,7 +135,11 @@ function text_style_xml(t::TextBody) end function make_xml(t::TextBox, id::Int=1) - cNvPr = Dict("p:cNvPr" => [Dict("id" => "$id"), Dict("name" => "TextBox")]) + cNvPr = Dict("p:cNvPr" => Dict[Dict("id" => "$id"), Dict("name" => "TextBox")]) + if has_hyperlink(t) + push!(cNvPr["p:cNvPr"], hlink_xml(t.hlink)) + end + cNvSpPr = Dict("p:cNvSpPr" => Dict("txBox" => "1")) nvPr = Dict("p:nvPr" => missing) diff --git a/src/write.jl b/src/write.jl index de9d75c..0feefb0 100644 --- a/src/write.jl +++ b/src/write.jl @@ -172,6 +172,7 @@ function Base.write( mktemp(filedir) do temp_path, temp_out ZipWriter(temp_out; own_io=true) do w update_presentation_state!(p, template_reader) + update_slide_nrs!(p) write_relationships!(w, p) write_presentation!(w, p) write_slides!(w, p, template_reader) diff --git a/test/runtests.jl b/test/runtests.jl index dd28072..dbb816b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,5 +11,6 @@ end include("testConstructors.jl") include("testTables.jl") +include("testHyperlinks.jl") include("testSlideXML.jl") include("testWriting.jl") \ No newline at end of file diff --git a/test/testHyperlinks.jl b/test/testHyperlinks.jl new file mode 100644 index 0000000..35f09da --- /dev/null +++ b/test/testHyperlinks.jl @@ -0,0 +1,44 @@ + +@testset "Test Hyperlinks" begin + pres = Presentation() + s2 = Slide() + s3 = Slide() + + box = TextBox(content = "Hyperlinked text", hlink = s3) + + @testset "has hyperlink" begin + @test PPTX.has_hyperlink(box) + box_nolink = TextBox(content = "Hyperlinked text") + @test !PPTX.has_hyperlink(box_nolink) + end + + @testset "hyperlink xml" begin + xml = PPTX.hlink_xml(box.hlink) + @test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))" + end + + push!(s2, box) + push!(pres, s2) + push!(pres, s3) + + @testset "hyperlink xml after rid update" begin + # After updating the rId of s2 the hyperlink xml should also be updated + xml = PPTX.hlink_xml(box.hlink) + @test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))" + end + + # I malificently swap slides which should not affect hyperlinking + pres.slides[2] = s3 + pres.slides[3] = s2 + + @testset "update slide nrs" begin + PPTX.update_slide_nrs!(pres) + @test pres.slides[2].slide_nr == 2 + @test pres.slides[3].slide_nr == 3 + xml_rels = PPTX.relationship_xml(box.hlink) + # Chech that rId is still the same + @test xml_rels["Relationship"][1]["Id"] == "rId$(PPTX.rid(s3))" + @test xml_rels["Relationship"][3]["Target"] == "slide$(PPTX.slide_nr(s3)).xml" + end + +end \ No newline at end of file