blob: 2167dc2d6bd5c92511b6e0321b044846cb1c745b [file] [log] [blame]
Fletcher Woodruffdd635822020-04-23 12:49:39 -06001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "xml_util.h"
6
7#include <memory>
8#include <utility>
9#include <string>
10
11#include <base/containers/flat_map.h>
12#include <libxml/tree.h>
13
14namespace {
15
16struct XmlNodeDeleter {
17 void operator()(xmlNode* p) { xmlFreeNode(p); }
18};
19using UniqueXmlNodePtr = std::unique_ptr<xmlNode, XmlNodeDeleter>;
20
21struct XmlDocDeleter {
22 void operator()(xmlDoc* p) { xmlFreeDoc(p); }
23};
24using UniqueXmlDocPtr = std::unique_ptr<xmlDoc, XmlDocDeleter>;
25
26constexpr char kPwgNs[] = "pwg";
27constexpr char kScanNs[] = "scan";
28using namespace_map = base::flat_map<std::string, xmlNs*>;
29
30inline const xmlChar* XmlCast(const char* p) {
31 return reinterpret_cast<const xmlChar*>(p);
32}
33
34// Serialize the xml tree in |root| into a vector. Consumes |root| and frees
35// its memory.
36std::vector<uint8_t> Serialize(UniqueXmlNodePtr root) {
37 UniqueXmlDocPtr doc(xmlNewDoc(XmlCast("1.0")));
38 // |doc| takes ownership of |root| here.
39 xmlDocSetRootElement(doc.get(), root.release());
40
41 xmlChar* buf;
42 int length;
43 xmlDocDumpFormatMemoryEnc(doc.get(), &buf, &length, "UTF-8", true);
44 std::vector<uint8_t> xml(buf, buf + length);
45 xmlFree(buf);
46
47 return xml;
48}
49
50// Adds a new node with namespace |ns| called |name| as a child of |node|.
51// Returns a non-owning pointer to the new node.
52xmlNode* AddNode(xmlNode* node, xmlNs* ns, const std::string& name) {
53 xmlNode* child = xmlNewNode(ns, XmlCast(name.c_str()));
54 xmlAddChild(node, child);
55 return child;
56}
57
58// Adds a new node with namespace |ns| called |name| and containing |content|
59// as a child of |node|.
60void AddNodeWithContent(xmlNode* node,
61 xmlNs* ns,
62 const std::string& name,
63 const std::string& content) {
64 xmlNode* child = xmlNewNode(ns, XmlCast(name.c_str()));
65 xmlNodeSetContent(child, XmlCast(content.c_str()));
66 xmlAddChild(node, child);
67}
68
69UniqueXmlNodePtr SourceCapabilitiesAsXml(const SourceCapabilities& caps,
70 const std::string& name,
71 namespace_map* namespaces) {
72 xmlNs* pwg = (*namespaces)[kPwgNs];
73 xmlNs* scan = (*namespaces)[kScanNs];
74 UniqueXmlNodePtr root(xmlNewNode(scan, XmlCast(name.c_str())));
75
76 AddNodeWithContent(root.get(), scan, "MinWidth", "16");
77 AddNodeWithContent(root.get(), scan, "MaxWidth", "2550");
78 AddNodeWithContent(root.get(), scan, "MinHeight", "16");
79 AddNodeWithContent(root.get(), scan, "MaxHeight", "3507");
80 AddNodeWithContent(root.get(), scan, "MaxScanRegions", "1");
81
82 xmlNode* profiles = AddNode(root.get(), scan, "SettingProfiles");
83 xmlNode* profile = AddNode(profiles, scan, "SettingProfile");
84 xmlNode* color_modes = AddNode(profile, scan, "ColorModes");
85 for (const std::string& mode : caps.color_modes) {
86 AddNodeWithContent(color_modes, scan, "ColorMode", mode);
87 }
88 xmlNode* document_formats = AddNode(profile, scan, "DocumentFormats");
89 for (const std::string& format : caps.formats) {
90 AddNodeWithContent(document_formats, pwg, "DocumentFormat", format);
91 }
92
93 xmlNode* resolutions = AddNode(profile, scan, "SupportedResolutions");
94 xmlNode* discrete_resolutions =
95 AddNode(resolutions, scan, "DiscreteResolutions");
96 for (int resolution : caps.resolutions) {
97 xmlNode* r = AddNode(discrete_resolutions, scan, "DiscreteResolution");
98 AddNodeWithContent(r, scan, "XResolution", std::to_string(resolution));
99 AddNodeWithContent(r, scan, "YResolution", std::to_string(resolution));
100 }
101
102 xmlNode* intents = AddNode(root.get(), scan, "SupportedIntents");
103 AddNodeWithContent(intents, scan, "Intent", "Document");
104 AddNodeWithContent(intents, scan, "Intent", "TextAndGraphic");
105 AddNodeWithContent(intents, scan, "Intent", "Photo");
106 AddNodeWithContent(intents, scan, "Intent", "Preview");
107
108 AddNodeWithContent(root.get(), scan, "MaxOpticalXResolution", "2400");
109 AddNodeWithContent(root.get(), scan, "MaxOpticalYResolution", "2400");
110 AddNodeWithContent(root.get(), scan, "RiskyLeftMargin", "0");
111 AddNodeWithContent(root.get(), scan, "RiskyRightMargin", "0");
112 AddNodeWithContent(root.get(), scan, "RiskyTopMargin", "0");
113 AddNodeWithContent(root.get(), scan, "RiskyBottomMargin", "0");
114
115 return root;
116}
117
Fletcher Woodruff0afc5162020-05-18 16:11:59 -0600118// Serialize a map of UUIDs to JobInfo into the XML format expected for
119// eSCL ScannerStatus.
120// Returns a node which can be merged into the larger XML document.
121UniqueXmlNodePtr JobListAsXml(const base::flat_map<std::string, JobInfo>& jobs,
122 namespace_map* namespaces) {
123 xmlNs* pwg = (*namespaces)[kPwgNs];
124 xmlNs* scan = (*namespaces)[kScanNs];
125
126 UniqueXmlNodePtr root(xmlNewNode(scan, XmlCast("Jobs")));
127 for (const auto& job : jobs) {
128 const std::string& uuid = job.first;
129 const JobInfo& info = job.second;
130 xmlNode* job_info = AddNode(root.get(), scan, "JobInfo");
131 AddNodeWithContent(job_info, pwg, "JobUri", "/eSCL/ScanJobs/" + uuid);
132 AddNodeWithContent(job_info, pwg, "JobUuid", "urn:uuid:" + uuid);
133
134 // Different scanners are not consistent with how they report scan job age.
135 // Arbitrarily report age as elapsed seconds.
136 base::TimeDelta elapsed = base::TimeTicks::Now() - info.created;
137 AddNodeWithContent(job_info, scan, "Age",
138 std::to_string(elapsed.InSeconds()));
139
140 int images_completed = 0;
141 int images_to_transfer = 0;
142 std::string job_state;
143 // These reason strings are defined in the PWG standard. There are other
144 // values possible, but for now, just use a typical value.
145 std::string reason;
146 switch (info.state) {
147 case kPending:
148 images_completed = 1;
149 images_to_transfer = 1;
150 job_state = "Pending";
151 reason = "JobScanning";
152 break;
153 case kCanceled:
154 images_completed = 0;
155 images_to_transfer = 0;
156 job_state = "Canceled";
157 reason = "JobTimedOut";
158 break;
159 case kCompleted:
160 images_completed = 1;
161 images_to_transfer = 0;
162 job_state = "Completed";
163 reason = "JobCompletedSuccessfully";
164 break;
165 }
166
167 AddNodeWithContent(job_info, pwg, "ImagesCompleted",
168 std::to_string(images_completed));
169 AddNodeWithContent(job_info, pwg, "ImagesToTransfer",
170 std::to_string(images_to_transfer));
171 AddNodeWithContent(job_info, pwg, "JobState", job_state);
172 xmlNode* job_state_reasons = AddNode(job_info, pwg, "JobStateReasons");
173 AddNodeWithContent(job_state_reasons, pwg, "JobStateReason", reason);
174 }
175
176 return root;
177}
178
Fletcher Woodruffdd635822020-04-23 12:49:39 -0600179} // namespace
180
181std::vector<uint8_t> ScannerCapabilitiesAsXml(const ScannerCapabilities& caps) {
182 UniqueXmlNodePtr root(xmlNewNode(nullptr, XmlCast("ScannerCapabilities")));
183
184 xmlNs* pwg =
185 xmlNewNs(root.get(), XmlCast("http://www.pwg.org/schemas/2010/12/sm"),
186 XmlCast("pwg"));
187 xmlNs* scan = xmlNewNs(
188 root.get(), XmlCast("http://schemas.hp.com/imaging/escl/2011/05/03"),
189 XmlCast("scan"));
190 xmlNewNs(root.get(), XmlCast("http://www.w3.org/2001/XMLSchema-instance"),
191 XmlCast("xsi"));
192 xmlSetNs(root.get(), scan);
193
194 namespace_map namespaces;
195 namespaces[kPwgNs] = pwg;
196 namespaces[kScanNs] = scan;
197
198 AddNodeWithContent(root.get(), pwg, "Version", "2.63");
199 AddNodeWithContent(root.get(), pwg, "MakeAndModel", caps.make_and_model);
200 AddNodeWithContent(root.get(), pwg, "SerialNumber", caps.serial_number);
201
202 xmlNode* platen = AddNode(root.get(), scan, "Platen");
203 UniqueXmlNodePtr platen_caps = SourceCapabilitiesAsXml(
204 caps.platen_capabilities, "PlatenInputCaps", &namespaces);
205 xmlAddChild(platen, platen_caps.release());
206
207 return Serialize(std::move(root));
208}
Fletcher Woodruff70ccf552020-04-17 14:08:06 -0600209
210std::vector<uint8_t> ScannerStatusAsXml(const ScannerStatus& status) {
211 UniqueXmlNodePtr root(xmlNewNode(nullptr, XmlCast("ScannerStatus")));
212
213 xmlNs* scan = xmlNewNs(
214 root.get(), XmlCast("http://schemas.hp.com/imaging/escl/2011/05/03"),
215 XmlCast("scan"));
216 xmlNs* pwg =
217 xmlNewNs(root.get(), XmlCast("http://www.pwg.org/schemas/2010/12/sm"),
218 XmlCast("pwg"));
219 xmlNewNs(root.get(), XmlCast("http://www.w3.org/2001/XMLSchema-instance"),
220 XmlCast("xsi"));
221 xmlSetNs(root.get(), scan);
222
223 AddNodeWithContent(root.get(), pwg, "Version", "2.6.3");
224 AddNodeWithContent(root.get(), pwg, "State", status.idle ? "Idle" : "Busy");
225
Fletcher Woodruff0afc5162020-05-18 16:11:59 -0600226 // Add a list of all of the scan jobs
227 namespace_map namespaces;
228 namespaces[kPwgNs] = pwg;
229 namespaces[kScanNs] = scan;
230 UniqueXmlNodePtr jobs = JobListAsXml(status.jobs, &namespaces);
231 xmlAddChild(root.get(), jobs.release());
232
Fletcher Woodruff70ccf552020-04-17 14:08:06 -0600233 return Serialize(std::move(root));
234}