dbus-top: initial commits

This commit covers the basic functionalities of the dbus-top tool.

The UI is divided into 3 windows as follows:

  +--------------------------+  Window list
  |         Window A         |  A: Summary statistics
  +------------+-------------+  B: Sensor list or detail
  |  Window B  |   Window C  |  C: Detailed statistics
  +------------+-------------+

To navigate the UI:
* Use tab to navigate each window

When a window is highlighted:
  In Window B:
  * Press esc key 3 times to leave the current sensor selection

  In Window C:
  * Press [Enter] to show/hide pop-up menu for column selectio
  * Press [Left]  to move highlight cursor to the left
  * Press [Right] to move highlight cursor to the right
  * Press [A] to sort by the highlighted column in ascending order
  * Press [D] to sort by the highlighted column in descending order

To add recipe to Yocto and build the recipe:
1) Copy and paste the content of the .bb file into a folder that can be
   detected by bitbake, such as meta-phosphor/recipes-phosphor/ipmi.
2) run "devtool modify -n dbus-top (path_to_openbmc_tools)/dbus-top/".

Signed-off-by: Adedeji Adebisi <adedejiadebisi01@gmail.com>
Change-Id: Id58ba30b815cfd9d18f54cf477d749dbdbc4545b
diff --git a/dbus-top/xmlparse.cpp b/dbus-top/xmlparse.cpp
new file mode 100644
index 0000000..2e4b1a3
--- /dev/null
+++ b/dbus-top/xmlparse.cpp
@@ -0,0 +1,193 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "xmlparse.hpp"
+#include "main.hpp"
+
+int Munch(const std::string& sv, int* idx, std::string* out)
+{
+    if (*idx >= static_cast<int>(sv.size()))
+        return -INVALID;
+    while (::isspace(sv[*idx]))
+    {
+        (*idx)++;
+    }
+    int ret = 0;
+    *out = "";
+    int quote_state = 0; // 0: not seen, 1: seen opening quotation, 2: ended
+    while (*idx < static_cast<int>(sv.size()))
+    {
+        const char ch = sv[*idx];
+        if (::isspace(ch) && quote_state != 1)
+        {
+            break;
+        }
+        (*idx)++;
+        if (ch == '<')
+        {
+            if (*idx < static_cast<int>(sv.size()) && sv[*idx] == '!')
+            {
+                ret = 10; // Comment
+            }
+            else if (*idx < static_cast<int>(sv.size()) && sv[*idx] == '/')
+            {
+                ret = 22; // Closing tag
+                (*idx)++; // Skip the '/'
+            }
+            else
+            {
+                ret = 1; // <
+            }
+        }
+        else if (ch == '>')
+        {
+            if (ret == 1)
+            {
+                ret = 12;
+            } // < >
+            else if (ret == 22)
+            {}
+            else
+                ret = 2; //   >
+            if (out->size() == 0)
+            {
+                (*idx)++;
+            }
+            break; // Do not consume
+        }
+        else if (ch == '\"')
+        {
+            ret = 3; //
+            switch (quote_state)
+            {
+                case 0:
+                {
+                    quote_state = 1;
+                    continue;
+                }
+                case 1:
+                {
+                    quote_state = 2;
+                    break;
+                }
+            }
+        }
+        else if (ch == '/' && *idx < static_cast<int>(sv.size()) &&
+                 sv[*idx] == '>')
+        {
+            ret = 22; // Closing tag
+            (*idx)++;
+            break;
+        }
+        else
+        {
+            out->push_back(ch);
+        }
+    }
+    return ret;
+}
+
+XMLNode* ParseXML(const std::string& sv)
+{
+    int verbose = 0;
+    char* v = getenv("VERBOSE");
+    if (v)
+    {
+        verbose = std::atoi(v);
+    }
+    int idx = 0;
+    std::string out;
+    int res;
+    std::vector<std::string> tags;
+    std::vector<XMLNode*> nodestack;
+    XMLNode* root = nullptr;
+    if (verbose > 0)
+    {
+        printf("%s\n", sv.c_str());
+    }
+    while ((res = Munch(sv, &idx, &out)) != -INVALID)
+    {
+        if (res == 1 || res == 12)
+        {
+            XMLNode* newnode = new XMLNode(out);
+            if (tags.empty())
+            {
+                root = newnode;
+            }
+            else
+            {
+                nodestack.back()->AddChild(newnode);
+            }
+            tags.push_back(out);
+            nodestack.push_back(newnode);
+        }
+
+        // Add name (has to be before pop_back)
+        if (out.find("name=") == 0)
+        {
+            nodestack.back()->SetName(out.substr(5));
+        }
+
+        if (res == 22 && tags.size() > 0)
+        {
+            tags.pop_back();
+            nodestack.pop_back();
+        }
+        if (verbose >= 2)
+        {
+            printf("Munch %d %s, tags:", res, out.c_str());
+            for (const std::string& x : tags)
+            {
+                printf(" %s", x.c_str());
+            }
+            printf("\n");
+        }
+    }
+    return root;
+}
+
+void DeleteTree(XMLNode* x)
+{
+    for (XMLNode* ch : x->children)
+    {
+        DeleteTree(ch);
+    }
+    delete x;
+}
+
+std::vector<std::string> XMLNode::GetChildNodeNames()
+{
+    std::vector<std::string> ret;
+    for (XMLNode* n : children)
+    {
+        if (n->tag == "node")
+        {
+            ret.push_back(n->fields["name"]);
+        }
+    }
+    return ret;
+}
+
+std::vector<std::string> XMLNode::GetInterfaceNames()
+{
+    std::vector<std::string> ret;
+    for (XMLNode* n : children)
+    {
+        if (n->tag == "interface")
+        {
+            ret.push_back(n->fields["name"]);
+        }
+    }
+    return ret;
+}
\ No newline at end of file