Skip to content

Commit

Permalink
Fix Utils::SortedTreeNode to wrap all /Kids entries
Browse files Browse the repository at this point in the history
If a /Kids entry, i.e. another node, is not correctly wrapped with the
same type class, the Dictionary#[] method doesn't work as expected which
yields errors down the road.
  • Loading branch information
gettalong committed Nov 4, 2024
1 parent 3da3b7a commit 7b0bda7
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* Parsing of indirect objects the value of which is an indirect reference
* Writing of the initial cross-reference section to ensure a single subsection
* [HexaPDF::Utils::SortedTreeNode] to wrap all /Kids entries with the correct
type class


## 1.0.0 - 2024-10-26
Expand Down
14 changes: 12 additions & 2 deletions lib/hexapdf/utils/sorted_tree_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def find_entry(key)
elsif node.key?(:Kids)
index = find_in_intermediate_node(node[:Kids], key)
node = node[:Kids][index]
node = document.wrap(node, type: self.class) if node
break unless node && key >= node[:Limits][0] && key <= node[:Limits][1]
else
break
Expand All @@ -194,7 +195,7 @@ def each_entry
container_name = leaf_node_container_name
stack = [self]
until stack.empty?
node = stack.pop
node = document.wrap(stack.pop, type: self.class)
if node.key?(container_name)
data = node[container_name]
index = 0
Expand All @@ -217,7 +218,7 @@ def each_entry
def path_to_key(node, key, stack)
return unless node.key?(:Kids)
index = find_in_intermediate_node(node[:Kids], key)
stack << node[:Kids][index]
stack << document.wrap(node[:Kids][index], type: self.class)
path_to_key(stack.last, key, stack)
end

Expand Down Expand Up @@ -307,6 +308,15 @@ def perform_validation
super
container_name = leaf_node_container_name

if key?(:Kids)
self[:Kids].each do |kid|
unless kid.indirect?
yield("Children of sorted tree nodes must be indirect", true)
document.add(kid)
end
end
end

# All keys of the container must be lexically ordered strings and the container must be
# correctly formatted
if key?(container_name)
Expand Down
13 changes: 7 additions & 6 deletions test/hexapdf/utils/test_sorted_tree_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
end

def add_multilevel_entries
@kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', 1, 'f', 1]}, type: HexaPDF::NameTreeNode)
item = @doc.add(1)
@item_ref = HexaPDF::Reference.new(item.oid, item.gen)
@kid11 = @doc.add({Limits: ['c', 'f'], Names: ['c', @item_ref, 'f', 1]}, type: HexaPDF::NameTreeNode)
@kid12 = @doc.add({Limits: ['i', 'm'], Names: ['i', 1, 'm', 1]}, type: HexaPDF::NameTreeNode)
ref = HexaPDF::Reference.new(@kid11.oid, @kid11.gen)
@kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]}, type: HexaPDF::NameTreeNode)
@kid1 = @doc.add({Limits: ['c', 'm'], Kids: [ref, @kid12]})
@kid21 = @doc.add({Limits: ['o', 'q'], Names: ['o', 1, 'q', 1]}, type: HexaPDF::NameTreeNode)
@kid221 = @doc.add({Limits: ['s', 'u'], Names: ['s', 1, 'u', 1]}, type: HexaPDF::NameTreeNode)
@kid22 = @doc.add({Limits: ['s', 'u'], Kids: [@kid221]}, type: HexaPDF::NameTreeNode)
Expand Down Expand Up @@ -75,7 +77,7 @@ def add_multilevel_entries
@root.add_entry('v', 1)
assert_equal(['a', 'm'], @kid1[:Limits].value)
assert_equal(['a', 'f'], @kid11[:Limits].value)
assert_equal(['a', 1, 'c', 1, 'e', 1, 'f', 1], @kid11[:Names].value)
assert_equal(['a', 1, 'c', @item_ref, 'e', 1, 'f', 1], @kid11[:Names].value)
assert_equal(['g', 'm'], @kid12[:Limits].value)
assert_equal(['g', 1, 'i', 1, 'j', 1, 'm', 1], @kid12[:Names].value)
assert_equal(['n', 'v'], @kid2[:Limits].value)
Expand Down Expand Up @@ -203,13 +205,12 @@ def add_multilevel_entries
end

it "checks that all kid objects are indirect objects" do
@root[:Kids][0] = ref = HexaPDF::Reference.new(@kid1.oid, @kid1.gen)
assert(@root.validate)

@root[:Kids][0] = ref
@root[:Kids][0] = @kid1
@kid1.oid = 0
assert(@root.validate do |message, c|
assert_match(/must be an indirect object/, message)
assert_match(/children.*must be indirect/i, message)
assert(c)
end)
assert(@kid1.indirect?)
Expand Down

0 comments on commit 7b0bda7

Please sign in to comment.