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