blob: 5f61ef0bd1c5ab71ad91d5a2253f94ac41363936 [file] [log] [blame] [edit]
// Copyright 2017 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fpdfapi/parser/cpdf_object_walker.h"
#include <sstream>
#include <string>
#include <utility>
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_boolean.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_null.h"
#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_reference.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/cpdf_string.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
std::string Walk(CPDF_Object* object) {
std::ostringstream result;
CPDF_ObjectWalker walker(object);
while (const CPDF_Object* obj = walker.GetNext()) {
if (obj->IsDictionary())
result << " Dict";
else if (obj->IsArray())
result << " Arr";
else if (obj->IsString())
result << " Str";
else if (obj->IsBoolean())
result << " Bool";
else if (obj->IsStream())
result << " Stream";
else if (obj->IsReference())
result << " Ref";
else if (obj->IsNumber())
result << " Num";
else if (obj->IsNull())
result << " Null";
else
result << " Unknown";
}
std::string result_str = result.str();
if (!result_str.empty()) {
result_str.erase(result_str.begin()); // remove start space
}
return result_str;
}
} // namespace
TEST(CPDF_ObjectWalkerTest, Simple) {
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_Null>().Get()), "Null");
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_Dictionary>().Get()), "Dict");
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_Array>().Get()), "Arr");
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_String>().Get()), "Str");
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_Boolean>().Get()), "Bool");
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_Stream>().Get()), "Stream");
EXPECT_EQ(Walk(pdfium::MakeRetain<CPDF_Reference>(nullptr, 0).Get()), "Ref");
}
TEST(CPDF_ObjectWalkerTest, CombinedObject) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetFor("1", pdfium::MakeRetain<CPDF_String>());
dict->SetFor("2", pdfium::MakeRetain<CPDF_Boolean>());
auto array = pdfium::MakeRetain<CPDF_Array>();
array->Append(pdfium::MakeRetain<CPDF_Reference>(nullptr, 0));
array->Append(pdfium::MakeRetain<CPDF_Null>());
array->Append(pdfium::MakeRetain<CPDF_Stream>(
nullptr, 0, pdfium::MakeRetain<CPDF_Dictionary>()));
dict->SetFor("3", std::move(array));
// The last number for stream length.
EXPECT_EQ(Walk(dict.Get()), "Dict Str Bool Arr Ref Null Stream Dict Num");
}
TEST(CPDF_ObjectWalkerTest, GetParent) {
auto level_4 = pdfium::MakeRetain<CPDF_Number>(0);
auto level_3 = pdfium::MakeRetain<CPDF_Dictionary>();
level_3->SetFor("Length", std::move(level_4));
auto level_2 =
pdfium::MakeRetain<CPDF_Stream>(nullptr, 0, std::move(level_3));
auto level_1 = pdfium::MakeRetain<CPDF_Array>();
level_1->Append(std::move(level_2));
auto level_0 = pdfium::MakeRetain<CPDF_Dictionary>();
level_0->SetFor("Array", std::move(level_1));
// We have <</Array [ stream( << /Length 0 >>) ]>>
// In this case each step will increase depth.
// And on each step the prev object should be parent for current.
const CPDF_Object* cur_parent = nullptr;
CPDF_ObjectWalker walker(level_0.Get());
while (const CPDF_Object* obj = walker.GetNext()) {
EXPECT_EQ(cur_parent, walker.GetParent());
cur_parent = obj;
}
}
TEST(CPDF_ObjectWalkerTest, SkipWalkIntoCurrentObject) {
auto root_array = pdfium::MakeRetain<CPDF_Array>();
// Add 2 null objects into |root_array|. [ null1, null2 ]
root_array->AppendNew<CPDF_Null>();
root_array->AppendNew<CPDF_Null>();
// |root_array| will contain 4 null objects after this.
// [ null1, null2, [ null3, null4 ] ]
root_array->Append(root_array->Clone());
int non_array_objects = 0;
CPDF_ObjectWalker walker(root_array.Get());
while (const CPDF_Object* obj = walker.GetNext()) {
if (obj != root_array && obj->IsArray()) {
// skip other array except root.
walker.SkipWalkIntoCurrentObject();
}
if (!obj->IsArray())
++non_array_objects;
}
// 2 objects from child array should be skipped.
EXPECT_EQ(2, non_array_objects);
}
TEST(CPDF_ObjectWalkerTest, DictionaryKey) {
auto dict = pdfium::MakeRetain<CPDF_Dictionary>();
dict->SetFor("1", pdfium::MakeRetain<CPDF_Null>());
dict->SetFor("2", pdfium::MakeRetain<CPDF_Null>());
dict->SetFor("3", pdfium::MakeRetain<CPDF_Null>());
dict->SetFor("4", pdfium::MakeRetain<CPDF_Null>());
dict->SetFor("5", pdfium::MakeRetain<CPDF_Null>());
CPDF_ObjectWalker walker(dict.Get());
while (const CPDF_Object* obj = walker.GetNext()) {
if (dict == obj) {
// Ignore root dictinary object
continue;
}
// Test that, dictionary key is correct.
EXPECT_EQ(walker.GetParent()->AsDictionary()->GetObjectFor(
walker.dictionary_key()),
obj);
}
}