diff --git a/Project.toml b/Project.toml index e908f8c..f02bf76 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PPTX" uuid = "14a86994-10a4-4a7d-b9ad-ef6f3b1fac6a" authors = ["Xander de Vries", "Matthijs Cox"] -version = "0.9.0" +version = "0.9.1" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/src/AbstractShape.jl b/src/AbstractShape.jl index a689033..549e568 100644 --- a/src/AbstractShape.jl +++ b/src/AbstractShape.jl @@ -23,5 +23,8 @@ function _show_string(shape::AbstractShape, compact::Bool) return "$(typeof(shape))" end -hlink_xml(hlink) = Dict("a:hlinkClick" => Dict("rId" => "rId$(rid(hlink))", "action" => "ppaction://hlinksldjump")) +function hlink_xml(hlink, relationship_map::Dict) + rel_id = relationship_map[hlink] + Dict("a:hlinkClick" => Dict("r:id" => "rId$rel_id", "action" => "ppaction://hlinksldjump")) +end has_hyperlink(s::AbstractShape) = hasfield(typeof(s), :hlink) && !isnothing(s.hlink) \ No newline at end of file diff --git a/src/Picture.jl b/src/Picture.jl index 9a1e296..edd0263 100644 --- a/src/Picture.jl +++ b/src/Picture.jl @@ -99,13 +99,14 @@ function _show_string(p::Picture, compact::Bool) return show_string end -function make_xml(shape::Picture, id::Int) +function make_xml(shape::Picture, id::Int, relationship_map::Dict) cNvPr = Dict("p:cNvPr" => [Dict("id" => "$id"), Dict("name" => "Picture")]) cNvPicPr = Dict("p:cNvPicPr" => Dict("a:picLocks" => Dict("noChangeAspect" => "1"))) nvPr = Dict("p:nvPr" => missing) nvPicPr = Dict("p:nvPicPr" => [cNvPr, cNvPicPr, nvPr]) - blip = Dict("a:blip" => Dict("r:embed" => "rId$(rid(shape))")) + rel_id = relationship_map[shape] + blip = Dict("a:blip" => Dict("r:embed" => "rId$rel_id")) stretch = Dict("a:stretch" => Dict("a:fillRect" => missing)) blipFill = Dict("p:blipFill" => [blip, stretch]) @@ -137,10 +138,10 @@ function filename(p::Picture) return "$(source_filename)_$(p._uuid)$extension" end -function relationship_xml(p::Picture) +function relationship_xml(p::Picture, r_id::Integer) return Dict( "Relationship" => [ - Dict("Id" => "rId$(rid(p))"), + Dict("Id" => "rId$r_id"), Dict("Type" => type_schema(p)), Dict("Target" => "../media/$(filename(p))"), ], diff --git a/src/Slide.jl b/src/Slide.jl index 28b6be1..28f2f7f 100644 --- a/src/Slide.jl +++ b/src/Slide.jl @@ -91,14 +91,14 @@ function Base.push!(slide::Slide, shape::AbstractShape) end end -function make_slide(s::Slide)::AbstractDict +function make_slide(s::Slide, relationship_map::Dict = slide_relationship_map(s))::AbstractDict xml_slide = OrderedDict("p:sld" => main_attributes()) spTree = init_sptree() initial_max_id = 1 for (index, shape) in enumerate(shapes(s)) id = index + initial_max_id - push!(spTree["p:spTree"], make_xml(shape, id)) + push!(spTree["p:spTree"], make_xml(shape, id, relationship_map)) end push!(xml_slide["p:sld"], OrderedDict("p:cSld" => [spTree])) @@ -148,17 +148,32 @@ function type_schema(s::Slide) return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" end -function relationship_xml(s::Slide) +function relationship_xml(s::Slide, r_id::Integer) return Dict( "Relationship" => [ - Dict("Id" => "rId$(rid(s))"), + Dict("Id" => "rId$r_id"), Dict("Type" => type_schema(s)), Dict("Target" => slide_fname(s)), ], ) end -function make_slide_relationships(s::Slide)::AbstractDict +function slide_relationship_map(s::Slide) + d = Dict{Union{Slide, AbstractShape}, Int}() + r_id = 1 # first rid is reserved by slideLayout + for shape in shapes(s) + if has_rid(shape) && !haskey(d, shape) + r_id += 1 + d[shape] = r_id + elseif has_hyperlink(shape) && !haskey(d, shape.hlink) + r_id += 1 + d[shape.hlink] = r_id + end + end + return d +end + +function make_slide_relationships(s::Slide, relationship_map::Dict = slide_relationship_map(s))::AbstractDict xml_slide_rels = OrderedDict("Relationships" => Dict[]) push!( xml_slide_rels["Relationships"], @@ -176,12 +191,20 @@ function make_slide_relationships(s::Slide)::AbstractDict ), ), ) + used_r_ids = [1] for shape in shapes(s) + r_id = 1 if has_rid(shape) - push!(xml_slide_rels["Relationships"], relationship_xml(shape)) + r_id = relationship_map[shape] + r_shape = shape end if has_hyperlink(shape) - push!(xml_slide_rels["Relationships"], relationship_xml(shape.hlink)) + r_id = relationship_map[shape.hlink] + r_shape = shape.hlink + end + if r_id ∉ used_r_ids + push!(xml_slide_rels["Relationships"], relationship_xml(r_shape, r_id)) + push!(used_r_ids, r_id) end end return xml_slide_rels diff --git a/src/Tables.jl b/src/Tables.jl index 9997f14..e879a80 100644 --- a/src/Tables.jl +++ b/src/Tables.jl @@ -97,7 +97,7 @@ function _show_string(t::Table, compact::Bool) return show_string end -function make_xml(t::Table, id::Integer) +function make_xml(t::Table, id::Integer, relationship_map::Dict = Dict()) nvGraphicFramePr = make_nvGraphicFramePr(t, id) xfrm = make_xfrm(t) tbl = make_xml_table(t) diff --git a/src/TextBox.jl b/src/TextBox.jl index 8f41ad9..1c8e0bf 100644 --- a/src/TextBox.jl +++ b/src/TextBox.jl @@ -191,10 +191,10 @@ function text_style_xml(t::TextStyle) return style end -function make_xml(t::TextBox, id::Int=1) +function make_xml(t::TextBox, id::Integer, relationship_map::Dict) cNvPr = Dict("p:cNvPr" => Dict[Dict("id" => "$id"), Dict("name" => "TextBox")]) if has_hyperlink(t) - push!(cNvPr["p:cNvPr"], hlink_xml(t.hlink)) + push!(cNvPr["p:cNvPr"], hlink_xml(t.hlink, relationship_map)) end cNvSpPr = Dict("p:cNvSpPr" => Dict("txBox" => "1")) diff --git a/src/write.jl b/src/write.jl index 0feefb0..f1e88a9 100644 --- a/src/write.jl +++ b/src/write.jl @@ -73,7 +73,7 @@ function update_table_style!(w::ZipWriter, template::ZipBufferReader) nothing end -function add_contenttypes!(w::ZipWriter, template::ZipBufferReader) +function add_contenttypes!(w::ZipWriter, template::ZipBufferReader, pres::Presentation) path = "[Content_Types].xml" doc = EzXML.parsexml(zip_readentry(template, path)) r = root(doc) @@ -93,6 +93,12 @@ function add_contenttypes!(w::ZipWriter, template::ZipBufferReader) isnothing(findfirst(x -> (x.name == "Default" && x["Extension"] == ext), elements(r))) || continue addelement!(r, "Default Extension=\"$ext\" ContentType=\"$ct\"") end + for slide in pres.slides + slide_path = "/ppt/slides/slide$(slide.slide_nr).xml" + isnothing(findfirst(x -> (x.name == "Override" && x["PartName"] == slide_path), elements(r))) || continue + slide_ct = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" + addelement!(r, "Override PartName=\"$slide_path\" ContentType=\"$slide_ct\"") + end zip_newfile(w, path; compress=true) prettyprint(w, doc) zip_commitfile(w) @@ -178,7 +184,7 @@ function Base.write( write_slides!(w, p, template_reader) write_shapes!(w, p) update_table_style!(w, template_reader) - add_contenttypes!(w, template_reader) + add_contenttypes!(w, template_reader, p) # copy over any files from the template # but don't overwrite any files in w for i in zip_nentries(template_reader):-1:1 diff --git a/test/testHyperlinks.jl b/test/testHyperlinks.jl index 35f09da..12636e2 100644 --- a/test/testHyperlinks.jl +++ b/test/testHyperlinks.jl @@ -3,8 +3,10 @@ pres = Presentation() s2 = Slide() s3 = Slide() + s4 = Slide() box = TextBox(content = "Hyperlinked text", hlink = s3) + push!(s2, box) @testset "has hyperlink" begin @test PPTX.has_hyperlink(box) @@ -13,32 +15,31 @@ end @testset "hyperlink xml" begin - xml = PPTX.hlink_xml(box.hlink) - @test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))" + relationship_map = PPTX.slide_relationship_map(s2) + @test relationship_map[box.hlink] == 2 + xml = PPTX.hlink_xml(box.hlink, relationship_map) + @test xml["a:hlinkClick"]["r:id"] == "rId2" end - push!(s2, box) - push!(pres, s2) - push!(pres, s3) + # push the same link once more + box2 = TextBox(content = "Hyperlinked text", hlink = s3) + push!(s2, box2) - @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))" + @testset "duplicate hyperlink relations" begin + relationship_map = PPTX.slide_relationship_map(s2) + @test length(relationship_map) == 1 + @test relationship_map[box2.hlink] == 2 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" + box3 = TextBox(content = "Hyperlinked text", hlink = s4) + push!(s2, box3) + + @testset "multiple hyperlink relations" begin + relationship_map = PPTX.slide_relationship_map(s2) + @test length(relationship_map) == 2 + @test relationship_map[box.hlink] == 2 + @test relationship_map[box2.hlink] == 2 + @test relationship_map[box3.hlink] == 3 end end \ No newline at end of file