Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief serialization implementation for xerces plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : */
8 :
9 : #include "serializer.hpp"
10 : #include "util.hpp"
11 :
12 : #include <xercesc/dom/DOM.hpp>
13 : #include <xercesc/framework/LocalFileFormatTarget.hpp>
14 :
15 : #include <map>
16 :
17 : #include <kdbease.h>
18 : #include <kdblogger.h>
19 : #include <key.hpp>
20 :
21 : XERCES_CPP_NAMESPACE_USE
22 : using namespace std;
23 : using namespace kdb;
24 : using namespace xerces;
25 :
26 : namespace
27 : {
28 :
29 623 : DOMElement * findChildWithName (DOMNode const & elem, string const & name)
30 : {
31 2391 : for (auto child = elem.getFirstChild (); child != NULL; child = child->getNextSibling ())
32 : {
33 2221 : if (DOMNode::ELEMENT_NODE == child->getNodeType ())
34 : {
35 2214 : DOMElement * childElem = dynamic_cast<DOMElement *> (child);
36 4428 : if (name == toStr (childElem->getNodeName ())) return childElem;
37 : }
38 : }
39 : return nullptr;
40 : }
41 :
42 : // the name parameter is only used in debug mode for logging, not in production, so we suppress the warning
43 163 : void key2xml (DOMDocument & doc, DOMElement & elem, string const & name ELEKTRA_UNUSED, Key const & key)
44 : {
45 : ELEKTRA_LOG_DEBUG ("updating element %s", name.c_str ());
46 :
47 : // key value = element value
48 489 : if (!key.get<string> ().empty ())
49 : {
50 : ELEKTRA_LOG_DEBUG ("creating text for element %s: %s", name.c_str (), key.get<string> ().c_str ());
51 565 : elem.appendChild (doc.createTextNode (asXMLCh (key.get<string> ())));
52 : }
53 :
54 : // meta keys = attributes
55 489 : Key itKey = key.dup (); // We can't use nextMeta on const key
56 : itKey.rewindMeta ();
57 404 : while (Key const & meta = itKey.nextMeta ())
58 : {
59 117 : if (meta.getName () != ELEKTRA_XERCES_ORIGINAL_ROOT_NAME)
60 : {
61 : ELEKTRA_LOG_DEBUG ("creating attribute %s for element %s: %s", meta.getName ().c_str (), name.c_str (),
62 : meta.get<string> ().c_str ());
63 256 : elem.setAttribute (asXMLCh (meta.getName ()), asXMLCh (meta.get<string> ()));
64 : }
65 : }
66 163 : }
67 :
68 691 : DOMElement * prepareArrayNodes (DOMDocument & doc, KeySet const & ks, Key & currentPathKey, bool rootPos, string const & name,
69 : string const & actualName, DOMNode * current, map<Key, DOMElement *> & arrays)
70 : {
71 691 : if (rootPos) return nullptr;
72 :
73 528 : currentPathKey.addBaseName (name);
74 528 : auto it = arrays.find (currentPathKey);
75 1056 : Key arrayKey = currentPathKey.dup ();
76 2112 : arrayKey.addBaseName ("#");
77 :
78 : // now check if its scanned already, if not, scan it and create and map the node elements
79 1819 : if (arrays.find (arrayKey) == arrays.end () && it == arrays.end ())
80 : {
81 167 : arrays[arrayKey] = nullptr; // used as a marker not mapped to the DOM for now
82 668 : KeySet arrayKeys = ckdb::elektraArrayGet (currentPathKey.getKey (), ks.getKeySet ());
83 576 : for (auto ak : arrayKeys)
84 : {
85 : ELEKTRA_LOG_DEBUG ("Precreating array node %s", ak->getFullName ().c_str ());
86 75 : DOMElement * arrayNode = doc.createElement (asXMLCh (actualName));
87 25 : arrays[ak] = arrayNode;
88 25 : current->appendChild (arrayNode);
89 : }
90 : }
91 596 : return it != arrays.end () ? it->second : nullptr;
92 : }
93 :
94 163 : void appendKey (DOMDocument & doc, KeySet const & ks, Key const & parentKey, string const & originalRootName, Key const & key,
95 : map<Key, DOMElement *> & arrays)
96 : {
97 163 : DOMNode * current = &doc;
98 :
99 : // Find the key's insertion point, creating the path if non existent
100 : ELEKTRA_LOG_DEBUG ("serializing key %s", key.getFullName ().c_str ());
101 :
102 : // Strip the parentKey, as we use relative paths
103 163 : auto parentName = parentKey.begin ();
104 : auto name = key.begin ();
105 4305 : while (parentName != --parentKey.end () && name != key.end ())
106 : {
107 1272 : parentName++;
108 1272 : name++;
109 : }
110 :
111 326 : if (name == key.end ()) throw XercesPluginException ("Key " + key.getFullName () + " is not under " + parentKey.getFullName ());
112 :
113 : // restore original root element name if present
114 163 : const auto rootPos = name;
115 :
116 : // Now create the path
117 489 : Key currentPathKey = parentKey.dup ();
118 326 : string actualName;
119 163 : DOMElement * child = nullptr;
120 3090 : for (; name != key.end (); name++)
121 : {
122 2763 : actualName = !originalRootName.empty () && name == rootPos ? originalRootName : (*name);
123 : // If we are not at the root element we scan for array keys as those need special treatment and use their mapped node
124 2073 : DOMElement * arrayChild = prepareArrayNodes (doc, ks, currentPathKey, name == rootPos, *name, actualName, current, arrays);
125 : // skip the array part of the path, in xml we can have multiple elements with the same name directly
126 691 : child = arrayChild ? arrayChild : findChildWithName (*current, actualName);
127 :
128 691 : if (!child)
129 : {
130 : ELEKTRA_LOG_DEBUG ("creating path element %s", actualName.c_str ());
131 510 : child = doc.createElement (asXMLCh (actualName));
132 170 : current->appendChild (child);
133 : }
134 691 : current = child;
135 : }
136 :
137 : // Now we are at the key's insertion point and the last key name part, the loop has already set all our elements
138 163 : if (child) key2xml (doc, *child, actualName, key);
139 163 : }
140 :
141 8 : void ks2dom (DOMDocument & doc, Key const & parentKey, KeySet const & ks)
142 : {
143 16 : Key root = ks.lookup (parentKey);
144 : const string originalRootName =
145 55 : root.hasMeta (ELEKTRA_XERCES_ORIGINAL_ROOT_NAME) ? root.getMeta<string> (ELEKTRA_XERCES_ORIGINAL_ROOT_NAME) : "";
146 16 : map<Key, DOMElement *> arrays;
147 513 : for (auto const & k : ks)
148 163 : appendKey (doc, ks, parentKey, originalRootName, k, arrays);
149 8 : }
150 :
151 : } // namespace
152 :
153 9 : void xerces::serialize (Key const & parentKey, KeySet const & ks)
154 : {
155 9 : if (!parentKey.isValid ()) throw XercesPluginException ("Parent key is invalid");
156 30 : if (parentKey.get<string> ().empty ()) throw XercesPluginException ("No destination file specified as key value");
157 :
158 : ELEKTRA_LOG_DEBUG ("serializing relative to %s to file %s", parentKey.getFullName ().c_str (), parentKey.get<string> ().c_str ());
159 48 : DOMImplementation * impl = DOMImplementationRegistry::getDOMImplementation (asXMLCh ("Core"));
160 8 : if (impl != NULL)
161 : {
162 24 : XercesPtr<DOMDocument> doc (impl->createDocument ());
163 8 : ks2dom (*doc, parentKey, ks);
164 :
165 8 : DOMImplementationLS * implLS = dynamic_cast<DOMImplementationLS *> (impl->getImplementation ());
166 :
167 24 : XercesPtr<DOMLSSerializer> serializer (implLS->createLSSerializer ());
168 8 : DOMConfiguration * serializerConfig = serializer->getDomConfig ();
169 8 : if (serializerConfig->canSetParameter (XMLUni::fgDOMWRTFormatPrettyPrint, true))
170 8 : serializerConfig->setParameter (XMLUni::fgDOMWRTFormatPrettyPrint, true);
171 :
172 48 : LocalFileFormatTarget targetFile (asXMLCh (parentKey.get<string> ()));
173 16 : XercesPtr<DOMLSOutput> output (implLS->createLSOutput ());
174 8 : output->setByteStream (&targetFile);
175 :
176 24 : serializer->write (doc.get (), output.get ());
177 : }
178 : else
179 0 : throw XercesPluginException ("DOMImplementation not available");
180 8 : }
|