My XML is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:ev="http://www.w3.org/2001/xml-events" xml:lang="de">
<head>
<title>Some Title</title>
<link rel="stylesheet" type="text/css" href="../Styles/9783748258957.css"/>
</head>
<body>
<div id="Inhalt">
<p class="toc-ch"><span class="bi">4 Main Value</span></p>
<p class="toc-h1">4.1 Child Value 1</p>
<p class="toc-h1">4.2 Child Value 2</p>
<p class="toc-h1">4.3 Child Value 3</p>
<p class="toc-h2">4.3.1 Grand-child Value</p>
<p class="toc-h2">4.3.2 Grand-child Value</p>
<p class="toc-h2">4.3.3 Grand-child Value</p>
<p class="toc-h2">4.3.4 Grand-child Value</p>
<p class="toc-h1">4.4 Child Value 4</p>
<p class="toc-h2">4.4.1 Grand-child Value</p>
<p class="toc-h2">4.4.2 Grand-child Value</p>
<p class="toc-h2">4.4.3 Grand-child Value</p>
<p class="toc-h2">4.4.4 Grand-child Value</p>
<p class="toc-h1">4.5 Child Value 5</p>
</div>
</body>
</html>

I want the XML in the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="de">
<head>
<title>Some Title</title>
<link rel="stylesheet" type="text/css" href="../Styles/9783748258957.css"/>
</head>
<body>
<navMap>
<navPoint id="navPoint-01" playOrder="01"><navLabel><text>4 Main Value</text></navLabel>
    <navPoint id="navPoint-02" playOrder="02"><navLabel><text>4.1 Child Value 1</text></navLabel></navPoint>
    <navPoint id="navPoint-03" playOrder="03"><navLabel><text>4.2 Child Value 2</text></navLabel></navPoint>
    <navPoint id="navPoint-04" playOrder="04"><navLabel><text>4.3 Child Value 3</text></navLabel>
        <navPoint id="navPoint-05" playOrder="05"><navLabel><text>4.3.1 Grand-child Value</text></navLabel></navPoint>
        <navPoint id="navPoint-06" playOrder="06"><navLabel><text>4.3.2 Grand-child Value</text></navLabel></navPoint>
        <navPoint id="navPoint-07" playOrder="07"><navLabel><text>4.3.3 Grand-child Value</text></navLabel></navPoint>
        <navPoint id="navPoint-08" playOrder="08"><navLabel><text>4.3.4 Grand-child Value</text></navLabel></navPoint></navPoint>
    <navPoint id="navPoint-09" playOrder="09"><navLabel><text>4.4 Child Value 4</text></navLabel>
        <navPoint id="navPoint-10" playOrder="10"><navLabel><text>4.4.1 Grand-child Value</text></navLabel></navPoint>
        <navPoint id="navPoint-11" playOrder="11"><navLabel><text>4.4.2 Grand-child Value</text></navLabel></navPoint>
        <navPoint id="navPoint-12" playOrder="12"><navLabel><text>4.4.3 Grand-child Value</text></navLabel></navPoint>
        <navPoint id="navPoint-13" playOrder="13"><navLabel><text>4.4.4 Grand-child Value</text></navLabel></navPoint></navPoint>
    <navPoint id="navPoint-14" playOrder="14"><navLabel><text>4.5 Child Value 5</text></navLabel></navPoint></navPoint>
</navMap>
</body>
</ncx>

How do I achieve this using foreach loop?

I want to nest the elements based on the class attribute. So, toc-ch is the main element, toc-h1 goes under toc-ch and toc-h2 goes under toc-h1

I am unable to make it nested. Now this is only one <div id="inhalt"> and I have multiple <div> elements. The structure remains the same for each of the classes.

This is what I have done:

XDocument xtocdoc = XDocument.Load("epubv3TOC.xml");
XNamespace xtocNamespace = xtocdoc.Root.GetDefaultNamespace();
int navPointValue = 1;
int playOrdervalue = 1;

foreach (var value in breakslist)
{
    var valueElements = value.Descendants(htmlNamespace + "p").ToList();
    foreach (var values in valueElements)
        if (values.Attribute("class") != null)
        {
            if (values.Attribute("class") != null && values.Attribute("class").Value.Contains("ch"))
            {
                xtocdoc.Descendants(xtocNamespace + "navMap").FirstOrDefault(de => de != null)
                       .Add(new XElement(xtocNamespace + "navPoint", new XAttribute("id", "navPoint-" + navPointValue)
                                                            , new XAttribute("playOrder", playOrdervalue)
                                                            , new XElement(xtocNamespace + "navLabel"
                                                            , new XElement(xtocNamespace + "text", "ch"))));
                continue;
            }
            else if (values.Attribute("class") != null && values.Attribute("class").Value.Contains("h1"))
            {
                xtocdoc.Descendants(xtocNamespace + "navMap").FirstOrDefault(de => de != null)
                                                        .Elements(xtocNamespace + "navPoint")
                                                        .FirstOrDefault(el => el != null)
                                                        .Add(new XElement(xtocNamespace + "navPoint", new XAttribute("id", "navPoint-" + navPointValue)
                                                            , new XAttribute("playOrder", playOrdervalue)
                                                            , new XElement(xtocNamespace + "navLabel"
                                                            , new XElement(xtocNamespace + "text", "h1"))));
                continue;
            }
            else if (values.Attribute("class") != null && values.Attribute("class").Value.Contains("h2"))
            {
                xtocdoc.Descendants(xtocNamespace + "navMap").FirstOrDefault(de => de != null)
                                                        .Elements(xtocNamespace + "navPoint")
                                                        .FirstOrDefault(el => el != null)
                                                        .Add(new XElement(xtocNamespace + "navPoint", new XAttribute("id", "navPoint-" + navPointValue)
                                                            , new XAttribute("playOrder", playOrdervalue)
                                                            , new XElement(xtocNamespace + "navLabel"
                                                            , new XElement(xtocNamespace + "text", "h2"))));
                continue;
            }
        }
    }

2 Answers

0
jdweng On Best Solutions

Here is solution using Xml Linq.. Why is there two nested levels of navPoint? :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication110
{
    class Program
    {
        const string INPUT_FILENAME = @"c:\temp\test.xml";
        const string OUTPUT_FILENAME = @"c:\temp\test1.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(INPUT_FILENAME);
            XElement root = doc.Root;
            XNamespace ns = root.GetDefaultNamespace();
            XElement body = root.Element(ns + "body");

            string newBodyStr = "<body><navMap></navMap></body>";
            XElement newBodyX = XElement.Parse(newBodyStr);

            XElement navMap = newBodyX.Element("navMap");

            int count = 1;


            foreach (XElement p in body.Descendants(ns + "p"))
            {
                string innerText = (string)p;

                XElement newNavPoint = new XElement("navPoint", new object[] {
                    new XAttribute("id", "navPoint-" + count.ToString("d2")),
                    new XAttribute("playOrder", count.ToString("d2")),
                    new XElement("navLabel", new XElement("text", innerText))
                });
                navMap.Add(newNavPoint);
                count++;

            }

            body.ReplaceWith(newBodyX);
            doc.Save(OUTPUT_FILENAME);
        }
    }
}
1
Jon Skeet On

To handle the nesting, you need to keep track of your current top-level navPoint, and your current child navPoint (if any). You can then decide where to add a new element based on the class of the p element.

Here's some sample code to do that. It doesn't do anything with the title etc - I've focused on the nesting:

using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

class Program
{
    static void Main()
    {
        var input = XDocument.Load("test.xml");
        XNamespace ncx = "http://www.daisy.org/z3986/2005/ncx/";
        XNamespace xhtml = "http://www.w3.org/1999/xhtml";
        // Skipped creating all the rest of the structure: focusing on the
        // navMap
        var navMap = new XElement(ncx + "navMap");
        var inhalt = input.Descendants(xhtml + "div")
            .Single(div => (string) div.Attribute("id") == "Inhalt");

        XElement currentTop = null;
        XElement currentChild = null;

        int index = 1;
        foreach (var element in inhalt.Elements())
        {
            string id = $"navPoint-{index++:00}";
            var point = new XElement(ncx + "navPoint", new XAttribute("id", id));
            var navLabel = new XElement(ncx + "navLabel", element.Value);
            point.Add(navLabel);
            // TODO: playOrder attribute, text element etc. They're not important for nesting.
            switch (element.Attribute("class")?.Value)
            {
                case "toc-ch":
                    currentTop = point;
                    currentChild = null;
                    navMap.Add(point);
                    break;
                case "toc-h1":
                    if (currentTop == null)
                    {
                        throw new InvalidOperationException("toc-h1 with no toc-ch");
                    }
                    currentChild = point;
                    currentTop.Add(point);
                    break;
                case "toc-h2":
                    if (currentChild == null)
                    {
                        throw new InvalidOperationException("toc-h2 with no toc-h1");
                    }
                    currentChild.Add(point);
                    break;
                default:
                    throw new InvalidOperationException("Unknown class attribute");
            }
        }

        var output = new XDocument(new XElement(ncx + "ncx", navMap));
        var settings = new XmlWriterSettings { Indent = true };
        using (var writer = XmlWriter.Create(Console.Out, settings))
        {
            output.Save(writer);
        }
    }
}

The output is:

<?xml version="1.0" encoding="ibm850"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/">
  <navMap>
    <navPoint id="navPoint-01">
      <navLabel>4 Main Value</navLabel>
      <navPoint id="navPoint-02">
        <navLabel>4.1 Child Value 1</navLabel>
      </navPoint>
      <navPoint id="navPoint-03">
        <navLabel>4.2 Child Value 2</navLabel>
      </navPoint>
      <navPoint id="navPoint-04">
        <navLabel>4.3 Child Value 3</navLabel>
        <navPoint id="navPoint-05">
          <navLabel>4.3.1 Grand-child Value</navLabel>
        </navPoint>
        <navPoint id="navPoint-06">
          <navLabel>4.3.2 Grand-child Value</navLabel>
        </navPoint>
        <navPoint id="navPoint-07">
          <navLabel>4.3.3 Grand-child Value</navLabel>
        </navPoint>
        <navPoint id="navPoint-08">
          <navLabel>4.3.4 Grand-child Value</navLabel>
        </navPoint>
      </navPoint>
      <navPoint id="navPoint-09">
        <navLabel>4.4 Child Value 4</navLabel>
        <navPoint id="navPoint-10">
          <navLabel>4.4.1 Grand-child Value</navLabel>
        </navPoint>
        <navPoint id="navPoint-11">
          <navLabel>4.4.2 Grand-child Value</navLabel>
        </navPoint>
        <navPoint id="navPoint-12">
          <navLabel>4.4.3 Grand-child Value</navLabel>
        </navPoint>
        <navPoint id="navPoint-13">
          <navLabel>4.4.4 Grand-child Value</navLabel>
        </navPoint>
      </navPoint>
      <navPoint id="navPoint-14">
        <navLabel>4.5 Child Value 5</navLabel>
      </navPoint>
    </navPoint>
  </navMap>
</ncx>