Assuming your input is valid XML (unlike the example you provided) this can be done with the following code:
XDocument doc = XDocument.Parse(
"<p>This is a paragraph. <b>This is a bold tag.</b> <b>This is another bold tag.</b> <i>This is an italic tag.</i></p>",
LoadOptions.PreserveWhitespace
);
string valueWithoutXmlElements = doc.Root.Value;
doc.Root.SetValue(valueWithoutXmlElements);
string valueWithRootXmlElement = doc.Root.ToString();
Console.WriteLine(valueWithRootXmlElement);
Specifically I had to remove spaces inside tag names (so </b> instead of < /b > etc). I also removed the trailing punctuation as XML can't have text outside the root node. If you have to support invalid XML you will not be able to manipulate it with any thing using a proper XML syntax (XmlReader, XDocument, XmlDocument, …), but would have to do regular expressions or similar as already suggested.
The main trick you were missing is the flag:
LoadOptions.PreserveWhitespace
This instruct the XML parser to keep whitespace only text nodes (so spaces between XML element tags - spaces in and around text are always preserved).
Updating the value "with itself", then getting the XML by calling ToString() does look a bit ugly, but will get the result you want without doing any node manipulation on your own.
I use XDocument.Parse to keep the example in a single file - you can also specify the LoadOptions when using XDocument.Load.