2009-08-28 19 views
5

Quiero tomar un archivo XML y reemplazar el valor de un elemento. Por ejemplo, si mi archivo XML es el siguiente:Reemplazar el valor de un elemento XML? Sed expresión regular?

<abc> 
    <xyz>original</xyz> 
</abc> 

quiero reemplazar el valor original del elemento de XYZ, sea la que sea, por otra cadena para que el archivo resultante es el siguiente:

<abc> 
    <xyz>replacement</xyz> 
</abc> 

¿Cómo harías esto? Sé que podría escribir un programa Java para hacer esto, pero supongo que eso es excesivo para reemplazar el valor de un solo elemento y que esto podría hacerse fácilmente usando sed para hacer una sustitución usando una expresión regular. Sin embargo, soy menos que novato con ese comando y espero que algún alma amable leyendo esto pueda darme la expresión regular correcta para el trabajo.

Una idea es hacer algo como esto:

sed s/\<xyz\>.*\<\\xyz\>/\<xyz\>replacement\<\\xyz\>/ <original.xml >new.xml 

Tal vez sea mejor para mí simplemente reemplazar toda la línea del archivo con lo que yo quiero que sea, ya que voy a conocer el nombre del elemento y el nuevo valor que quiero usar? Pero esto supone que el elemento en cuestión está en una sola línea y que ningún otro dato XML está en la misma línea. Prefiero tener un comando que básicamente reemplazará el valor del elemento xyz con una nueva cadena que especifique y no tener que preocuparme si el elemento está todo en una línea o no, etc.

Si sed no es la mejor herramienta para este trabajo, por favor llámeme a un mejor enfoque.

Si alguien puede dirigirme en la dirección correcta, realmente lo agradeceré, probablemente me ahorrará horas de prueba y error. ¡Gracias por adelantado!

--James

Respuesta

4

OK, así que mordí la bala y me tomé el tiempo para escribir un programa Java que hace lo que quiero. A continuación se muestra el método operativo llamado por mi método main(), que hace el trabajo, en caso de que esto sea útil a alguien más en el futuro:

/** 
* Takes an input XML file, replaces the text value of the node specified by an XPath parameter, and writes a new 
* XML file with the updated data. 
* 
* @param inputXmlFilePathName 
* @param outputXmlFilePathName 
* @param elementXpath 
* @param elementValue 
* @param replaceAllFoundElements 
*/ 
public static void replaceElementValue(final String inputXmlFilePathName, 
             final String outputXmlFilePathName, 
             final String elementXpathExpression, 
             final String elementValue, 
             final boolean replaceAllFoundElements) 
{ 
    try 
    { 
     // get the template XML as a W3C Document Object Model which we can later write back as a file 
     InputSource inputSource = new InputSource(new FileInputStream(inputXmlFilePathName)); 
     DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 
     Document document = documentBuilderFactory.newDocumentBuilder().parse(inputSource); 

     // create an XPath expression to access the element's node 
     XPathFactory xpathFactory = XPathFactory.newInstance(); 
     XPath xpath = xpathFactory.newXPath(); 
     XPathExpression xpathExpression = xpath.compile(elementXpathExpression); 

     // get the node(s) which corresponds to the XPath expression and replace the value 
     Object xpathExpressionResult = xpathExpression.evaluate(document, XPathConstants.NODESET); 
     if (xpathExpressionResult == null) 
     { 
      throw new RuntimeException("Failed to find a node corresponding to the provided XPath."); 
     } 
     NodeList nodeList = (NodeList) xpathExpressionResult; 
     if ((nodeList.getLength() > 1) && !replaceAllFoundElements) 
     { 
      throw new RuntimeException("Found multiple nodes corresponding to the provided XPath and multiple replacements not specified."); 
     } 
     for (int i = 0; i < nodeList.getLength(); i++) 
     { 
      nodeList.item(i).setTextContent(elementValue); 
     } 

     // prepare the DOM document for writing 
     Source source = new DOMSource(document); 

     // prepare the output file 
     File file = new File(outputXmlFilePathName); 
     Result result = new StreamResult(file); 

     // write the DOM document to the file 
     Transformer transformer = TransformerFactory.newInstance().newTransformer(); 
     transformer.transform(source, result); 
    } 
    catch (Exception ex) 
    { 
     throw new RuntimeException("Failed to replace the element value.", ex); 
    } 
} 

corro el programa de este modo:

$ java -cp xmlutility.jar com.abc.util.XmlUtility input.xml output.xml '//name/text()' JAMES 
2

No me gusta ser un negativista, pero XML es cualquier cosa menos normal. Una expresión regular probablemente será más problemática de lo que vale. Consulte aquí para obtener más información: Using C# Regular expression to replace XML element content

Su idea de un simple programa Java podría ser agradable después de todo. Una transformación XSLT puede ser más fácil si conoces XSLT bastante bien. Si conoces a Perl ... esa es la manera de ir en mi humilde opinión.

Habiendo dicho eso, si eliges ir con un Regex y tu versión de sed admite expresiones regulares extendidas, puedes hacer que sea multilínea con/g. En otras palabras, ponga/g al final de la expresión regular y coincidirá con su patrón, incluso si están en múltiples líneas.

También. la Regex que usted propuso es "codiciosa". Capturará el mayor grupo de caracteres posible porque el "." coincidirá desde la primera aparición hasta la última. Puede hacerlo "flojo" cambiando el comodín a ".?". Poner el signo de interrogación después del asterisco le indicará que coincida solo con un conjunto de a.

+0

Estoy bastante seguro de que el modificador '/ g' en' sed' hace que se reemplace globalmente dentro de la línea, no se extienda a través de las líneas. Tampoco pensé que 'sed 'soportara expresiones regulares perezosas como esa, ciertamente no parece cuando lo intento. – Cascabel

6

sed no va a ser una herramienta fácil de usar para reemplazos multilínea. Es posible implementarlos usando su comando N y alguna recursión, verificando después de leer en cada línea si se ha encontrado el cierre de la etiqueta ... pero no es bonita y nunca la recordarás.

Por supuesto, en realidad análisis del XML y la sustitución de las etiquetas va a ser lo más seguro, pero si usted sabe que no se encontrará con algún problema, puede probar esto:

perl -p -0777 -e '[email protected]<xyz>.*?</xyz>@<xyz>new-value</xyz>@sg' <xml-file> 

Romper esto abajo :

  • -p dice que colocar a través de la entrada y de impresión
  • -0777 le dice que use el final del archivo como el separador de entrada, de modo que consiga todo en uno en slurp
  • -e significa aquí viene las cosas que quiero que haga

Y la sustitución en sí:

  • uso @ como delimitador por lo que no tiene que escapar /
  • uso *? , la versión no codiciosa, para que coincida lo menos posible, por lo que no vamos todo el camino hasta la última aparición de </xyz> en el archivo
  • utilizar el modificador s dejar . nuevas líneas de concordancia (para obtener los valores de las etiquetas de varias líneas)
  • utilice el modificador g para que coincida con el patrón varias veces

Tada! Esto imprime el resultado en stdout: una vez que verifique que hace lo que desea, agregue la opción -i para indicarle que edite el archivo en su lugar.

+2

me gusta esta solución porque es simple y perl es nativa en muchas distros de Linux – Michael

+0

Esto fue bastante útil. Estaba intentando hacer algo similar con sed, pero este script de Perl funcionó muy bien. Además, agregué el indicador -i para que escribiera en el archivo por mí. También fue bueno ver que Perl hizo un archivo de copia de seguridad automáticamente también. – mason81

0

Estaba tratando de hacer lo mismo y encontré este guión [gu] awk que lo logra.

BEGIN { FS = "[<|>]" } 
{ 
    if ($2 == "xyz") { 
     sub($3, "replacement")  
    } 
    print 
} 
Cuestiones relacionadas