private void ProcessChildNode(HashSet <XNode> invalidNodes, Delta newDelta, XNode node, ParseState state,
                                      JObject attributes = null)
        {
            switch (node)
            {
            case XElement elem:
                switch (elem.Name.LocalName)
                {
                case "para":
                    ProcessChildNodes(invalidNodes, newDelta, elem);
                    InsertPara(invalidNodes, newDelta, elem);
                    break;

                case "verse":
                    state.LastVerseStr = (string)elem.Attribute("number");
                    InsertVerse(invalidNodes, newDelta, elem, state);
                    break;

                case "ref":
                    var newRefAttributes = (JObject)attributes?.DeepClone() ?? new JObject();
                    newRefAttributes.Add(new JProperty(elem.Name.LocalName, GetAttributes(elem)));
                    newRefAttributes = AddInvalidInlineAttribute(invalidNodes, elem, newRefAttributes);
                    newDelta.InsertText(elem.Value, state.CurRef, newRefAttributes);
                    break;

                case "char":
                    var     newChildAttributes = (JObject)attributes?.DeepClone() ?? new JObject();
                    JToken  existingCharAttrs  = newChildAttributes["char"];
                    JObject newCharAttrs       = GetAttributes(elem);
                    if (!newCharAttrs.ContainsKey("cid"))
                    {
                        newCharAttrs.Add("cid", GuidService.Generate());
                    }

                    if (existingCharAttrs == null)
                    {
                        newChildAttributes.Add(new JProperty(elem.Name.LocalName, newCharAttrs));
                    }
                    else
                    {
                        switch (existingCharAttrs)
                        {
                        case JArray array:
                            array.Add(newCharAttrs);
                            break;

                        case JObject obj:
                            newChildAttributes[elem.Name.LocalName] = new JArray(obj, newCharAttrs);
                            break;
                        }
                    }
                    newChildAttributes = AddInvalidInlineAttribute(invalidNodes, elem, newChildAttributes);
                    if (!elem.Nodes().Any() && elem.Value == "")
                    {
                        newDelta.InsertEmpty(state.CurRef, newChildAttributes);
                    }
                    else
                    {
                        ProcessChildNodes(invalidNodes, newDelta, elem, state, newChildAttributes);
                    }
                    break;

                case "table":
                    state.TableIndex++;
                    JObject tableAttributes = GetAttributes(elem);
                    tableAttributes.Add(new JProperty("id", $"table_{state.TableIndex}"));
                    int rowIndex = 1;
                    foreach (XElement row in elem.Elements("row"))
                    {
                        var rowAttributes = new JObject(
                            new JProperty("id", $"row_{state.TableIndex}_{rowIndex}"));
                        int cellIndex = 1;
                        foreach (XElement cell in row.Elements())
                        {
                            state.CurRef = $"cell_{state.TableIndex}_{rowIndex}_{cellIndex}";
                            ProcessChildNode(invalidNodes, newDelta, cell, state);
                            SegmentEnded(newDelta, state.CurRef);
                            var attrs = new JObject(
                                new JProperty("table", tableAttributes),
                                new JProperty("row", rowAttributes));
                            if (cell.Name.LocalName == "cell")
                            {
                                attrs.Add(new JProperty("cell", GetAttributes(cell)));
                            }
                            attrs = AddInvalidBlockAttribute(invalidNodes, elem, attrs);
                            attrs = AddInvalidBlockAttribute(invalidNodes, row, attrs);
                            attrs = AddInvalidBlockAttribute(invalidNodes, cell, attrs);
                            newDelta.Insert("\n", attrs);
                            cellIndex++;
                        }
                        rowIndex++;
                    }
                    state.CurRef = null;
                    break;

                case "cell":
                    ProcessChildNodes(invalidNodes, newDelta, elem, state);
                    break;

                default:
                    InsertEmbed(invalidNodes, newDelta, elem, state.CurRef, attributes);
                    break;
                }
                break;

            case XText text:
                newDelta.InsertText(text.Value, state.CurRef,
                                    AddInvalidInlineAttribute(invalidNodes, text, attributes));
                break;
            }
        }