LCOV - code coverage report
Current view: top level - src/plugins/xerces - serializer.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 72 73 98.6 %
Date: 2019-09-12 12:28:41 Functions: 6 6 100.0 %

          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 : }

Generated by: LCOV version 1.13