Como posso usar alvo xmlpoke de Nant para remover um nó
Pergunta
Dado o seguinte xml:
<rootnode>
<childnode arg="a">Content A</childnode>
<childnode arg="b">Content A</childnode>
</rootnode>
Usando XMLPoke com o seguinte XPath:
rootnode/childnode[arg='b']
O resultado (se o texto de substituição está vazia) é:
<rootnode>
<childnode arg="a">Content A</childnode>
<childnode arg="b"></childnode>
</rootnode>
O conteúdo do childNode foram removidos quando nós realmente deseja que o childNode si removido. O resultado desejado é:
<rootnode>
<childnode arg="a">Content A</childnode>
</rootnode>
O childNode devem ser selecionados com base no argumento childNode.
Solução
Eu mantenho minhas mãos para cima! Este é um caso clássico de fazer a pergunta errada.
O problema é que você não pode usar xmlpoke para remover um único nó. Xmlpoke só pode ser usado para editar o conteúdo de um nó ou atributo específico. Não é uma maneira elegante para remover um nó filho como pela questão usando apenas o padrão Nant alvos. É pode ser feito usando alguma manipulação corda deselegante usando propriedades em Nant, mas por que você iria querer?
A melhor maneira de fazer isso é escrever um simples alvo Nant. Aqui está um que eu preparado anteriormente:
using System;
using System.IO;
using System.Xml;
using NAnt.Core;
using NAnt.Core.Attributes;
namespace XmlStrip
{
[TaskName("xmlstrip")]
public class XmlStrip : Task
{
[TaskAttribute("xpath", Required = true), StringValidator(AllowEmpty = false)]
public string XPath { get; set; }
[TaskAttribute("file", Required = true)]
public FileInfo XmlFile { get; set; }
protected override void ExecuteTask()
{
string filename = XmlFile.FullName;
Log(Level.Info, "Attempting to load XML document in file '{0}'.", filename );
XmlDocument document = new XmlDocument();
document.Load(filename);
Log(Level.Info, "XML document in file '{0}' loaded successfully.", filename );
XmlNode node = document.SelectSingleNode(XPath);
if(null == node)
{
throw new BuildException(String.Format("Node not found by XPath '{0}'", XPath));
}
node.ParentNode.RemoveChild(node);
Log(Level.Info, "Attempting to save XML document to '{0}'.", filename );
document.Save(filename);
Log(Level.Info, "XML document successfully saved to '{0}'.", filename );
}
}
}
Combine acima com uma modificação para o NAnt.exe.config arquivo para carregar o alvo personalizado e o seguinte script no construção arquivo:
<xmlstrip xpath="//rootnode/childnode[@arg = 'b']" file="target.xml" />
Isto irá remover o childNode com um argumento arg com valor b de target.xml . Que é o que eu realmente queria, em primeiro lugar!
Outras dicas
xmlpeek
só lê valores.
xmlpoke
só estabelece valores.
Infelizmente, nant não tem um xmldelete tarefa.
Eu resolvi esse problema criando um <target />
nant em um arquivo nant posso facilmente re-uso entre o projeto.
Eu escolhi a alavancagem da nant built-in tarefas <regex />
, <echo />
, <include />
e <call />
.
Vantagens:
- Funciona com expressão regular (ver desvantagens).
- Pode combinar com qualquer texto, incluindo XML!
Desvantagens:
- Você já resolveu um problema com um regex? Se sim, você agora você tem um outro problema!
- O valor regex no arquivo nant deve ser escapado! (Use uma ferramenta online escaper xml).
<!-- This file should be included before being called -->
<project name="YourProject">
<target name="RemoveLineFromFile">
<loadfile
file="${delete.from.file.path}"
property="xml.file.content" />
<property
name="delete.from.file.regex"
value="(?'BEFORE'[\w\s\W]*)\<add.*key=\"AWSProfileName\".*value=\"comsec\".*\/\>(?'AFTER'[\w\s\W]*)" />
<regex
input="${xml.file.content}"
pattern="${delete.from.file.regex}" />
<echo
file="${delete.from.file.path}"
message="${BEFORE}${AFTER}"
append="false" />
</target>
</project>
A seguir é como incluir e chamar esta meta. Tenha em mente os todos os parâmetros são globais e devem ser definidos antes de chamar o alvo.
-
delete.from.file.path
:. O caminho do arquivo a ser modificado -
delete.from.file.regex
:. A correspondência regex o que removeu (e definir antes e depois de grupos)
<project name="YourProject">
<include buildfile="path\to\nant\target\RemoveLineFromFile.build"/>
<target name="OtherWork">
<property name="delete.from.file.path" value="path\to\xml\file.xml" />
<property name="delete.from.file.regex" value="(?'BEFORE'[\w\s\W]*)\<childnode.*arg="b">(.*?)\<\/childnode\>(?'AFTER'[\w\s\W]*)" />
<call target="RemoveLineFromFile" />
</target>
</project>