1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "csv_string_resource_loader.h"
16
17 #include <cctype>
18
19 #include <meta/api/object.h>
20
21 #include "csv_parser.h"
22
META_BEGIN_NAMESPACE()23 META_BEGIN_NAMESPACE()
24
25 bool CsvStringResourceLoader::Build(const IMetadata::Ptr& data)
26 {
27 return true;
28 }
29
Reload()30 void CsvStringResourceLoader::Reload()
31 {
32 Reset();
33 META_ACCESS_EVENT(ContentChanged)->Invoke();
34 }
35
Reset()36 void CsvStringResourceLoader::Reset()
37 {
38 cachedContent_.clear();
39 }
40
Create(const IObject::Ptr & params)41 IObject::Ptr CsvStringResourceLoader::Create(const IObject::Ptr& params)
42 {
43 CsvContentType csv;
44 ResourceObjectOptions options;
45
46 // Check the loader parameters object for parameters which can be used to affect functionality
47 if (auto meta = interface_cast<IMetadata>(params)) {
48 if (const auto prop = meta->GetPropertyByName<BASE_NS::string>("KeyHeaderColumnName")) {
49 options.keyHeaderColumnName = prop->GetValue();
50 }
51 if (const auto prop = meta->GetPropertyByName<BASE_NS::string>("TargetPropertyName")) {
52 options.targetPropertyName = prop->GetValue();
53 }
54 if (const auto prop = meta->GetPropertyByName<bool>("KeysToLower")) {
55 options.keysToLower = prop->GetValue();
56 }
57 }
58
59 if (cachedContent_.empty() || !META_ACCESS_PROPERTY(Cached)->GetValue()) {
60 if (file_) {
61 auto size = file_->GetLength();
62 file_->Seek(0);
63 BASE_NS::string content;
64 content.resize(size);
65 if (file_->Read(content.data(), size) < size) {
66 CORE_LOG_E("Failed to read");
67 Reset();
68 } else {
69 csv = ParseCsv(content, options);
70 }
71 } else {
72 Reset();
73 }
74 } else {
75 csv.swap(cachedContent_);
76 }
77
78 IObject::Ptr result = CreateStringResourceObject(csv, options);
79
80 if (META_ACCESS_PROPERTY(Cached)->GetValue()) {
81 cachedContent_.swap(csv);
82 }
83
84 return result;
85 }
86
CreateStringResourceObject(const CsvContentType & content,const ResourceObjectOptions & options)87 IObject::Ptr CsvStringResourceLoader::CreateStringResourceObject(
88 const CsvContentType& content, const ResourceObjectOptions& options)
89 {
90 if (content.empty()) {
91 return {};
92 }
93
94 Object strings;
95 const BASE_NS::vector<BASE_NS::string>* keys = nullptr;
96
97 // Find the column which contains our keys
98 for (const auto& item : content) {
99 if (item.first == options.keyHeaderColumnName) {
100 keys = &item.second;
101 break;
102 }
103 }
104
105 if (!keys) {
106 CORE_LOG_E("Cannot find column '%s' from CSV header", options.keyHeaderColumnName.c_str());
107 return {};
108 }
109 auto& objectRegistry = META_NS::GetObjectRegistry();
110 // Create IObject property for each column, containing the items as properties of type BASE_NS::string
111 for (const auto& column : content) {
112 if (&column.second == keys) {
113 continue; // ignore the column whose header is <KeyHeaderColumn>
114 }
115 Object item;
116 for (size_t i = 0; i < column.second.size(); i++) {
117 item.Metadata().AddProperty(ConstructProperty<BASE_NS::string>(keys->at(i), column.second[i]));
118 }
119 strings.Metadata().AddProperty(ConstructProperty<IObject::Ptr>(column.first, item));
120 }
121
122 Object object;
123 object.MetaProperty(ConstructProperty<IObject::Ptr>(options.targetPropertyName, strings));
124 return object;
125 }
126
ParseCsv(BASE_NS::string_view csv,const ResourceObjectOptions & options)127 CsvStringResourceLoader::CsvContentType CsvStringResourceLoader::ParseCsv(
128 BASE_NS::string_view csv, const ResourceObjectOptions& options)
129 {
130 CsvParser parser(csv);
131 CsvContentType csvContent;
132 CsvParser::CsvRow header;
133
134 if (parser.GetRow(header); header.empty()) {
135 CORE_LOG_E("Failed to parse header");
136 return {};
137 }
138
139 for (auto& item : header) {
140 // Make sure that our column headers are in lower case
141 csvContent.push_back(CsvContentItemType { options.keysToLower ? item.lower() : item, {} });
142 }
143
144 CsvParser::CsvRow items;
145 unsigned long long lineNum = 0;
146 while (parser.GetRow(items)) {
147 lineNum++;
148 if (items.size() != header.size()) {
149 CORE_LOG_E("Number of items on line %llu does not match the number of CSV header items (%zu vs %zu)",
150 lineNum, items.size(), header.size());
151 return {};
152 }
153 for (size_t i = 0; i < csvContent.size(); i++) {
154 csvContent[i].second.push_back(items[i]);
155 }
156 }
157
158 return csvContent;
159 }
160
SetFile(CORE_NS::IFile::Ptr file)161 bool CsvStringResourceLoader::SetFile(CORE_NS::IFile::Ptr file)
162 {
163 file_ = BASE_NS::move(file);
164 Reload();
165 return true;
166 }
167
168 META_END_NAMESPACE()
169