OptionWriteEmptyNodes нарушает объявление XML с помощью HtmlAgilityPack

Вот супер простой код, который у меня есть:

HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.OptionWriteEmptyNodes = true;
htmlDoc.Load("sourcefilepath");
htmlDoc.Save("destfilepath", Encoding.UTF8);

Входные данные:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8"/>
    <link rel="stylesheet" href="main.css" type="text/css"/>
  </head>
  <body>lots of text here, obviously not relevant to this problem</body>
</html>

Выход:

<?xml version="1.0" encoding="UTF-8" />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
    <link rel="stylesheet" href="main.css" type="text/css" />
  </head>
  <body>lots of text here, obviously not relevant to this problem</body>
</html>

Вы можете видеть, что в первой строке есть ошибка: /> вместо?> Это происходит, если я устанавливаю OptionWriteEmptyNodes в истинное значение. Для него установлено значение true, поскольку в противном случае мета / теги ссылок (и некоторые другие в теле документа) не будут закрыты.

Кто-нибудь знает, как решить эту проблему?

3 ответа

Решение

Похоже, ошибка. Вы должны сообщить об этом на http://htmlagilitypack.codeplex.com/.

Тем не менее, вы можете обойти эту ошибку следующим образом:

HtmlNode.ElementsFlags.Remove("meta");
HtmlNode.ElementsFlags.Remove("link");
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.Load("sourcefilepath");
htmlDoc.Save("destfilepath", Encoding.UTF8);

Просто удалите флаги из meta & link теги, которые инструктируют Html Agility Pack не закрывать их автоматически и не устанавливают OptionWriteEmptyNodes в true,

Это произведет это (заметьте, что это немного отличается):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8"></meta>
    <link rel="stylesheet" href="main.css" type="text/css"></link>
  </head>
  <body>lots of text here, obviously not relevant to this problem</body>
</html>

Удалось сделать другой способ обойти эту проблему. Это работает немного лучше в моем случае, чем выше. По сути, мы заменяем первый дочерний элемент DocumentNode, который является объявлением xml (обратите внимание, что входные данные должны содержать объявление xml, в моем случае это 100%).

HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.OptionWriteEmptyNodes = true;
htmlDoc.Load("sourcepath");

var newNodeStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
var newNode = HtmlNode.CreateNode(newNodeStr);

htmlDoc.DocumentNode.ReplaceChild(newNode, htmlDoc.DocumentNode.FirstChild);


htmlDoc.Save("destpath", Encoding.UTF8);

Обратите внимание, что обходной путь Саймона тоже работает, поэтому возьмите тот, который лучше соответствует вашему сценарию.

Мои страницы также имеют <br/> теги в них и удаление htmlDoc.OptionWriteEmptyNodes = true; ломает те, заменяя их <br>, Я нашел подход, похожий на ответ Алекса, но немного более общий, чтобы сохранить большинство исходных значений, и не полагаться на то, что на вашей странице всегда будет тег xml:

HtmlDocument doc= new HtmlDocument();
doc.OptionWriteEmptyNodes = true;
doc.Load("pathToFile");
if (doc.DocumentNode.FirstChild.OriginalName.Equals("?xml"))
{
    var fixedOuterHtml = doc.DocumentNode.FirstChild.OuterHtml.Replace('/', '?');
    var newNode = HtmlNode.CreateNode(fixedOuterHtml);
    doc.DocumentNode.ReplaceChild(newNode, doc.DocumentNode.FirstChild);
}
Другие вопросы по тегам