Заменить значение элемента XML? Sed регулярное выражение? - PullRequest
5 голосов
/ 28 августа 2009

Я хочу взять файл XML и заменить значение элемента. Например, если мой XML-файл выглядит так:

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

Я хочу заменить исходное значение элемента xyz, каким бы оно ни было, другой строкой, чтобы результирующий файл выглядел так:

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

Как бы вы это сделали? Я знаю, что мог бы написать Java-программу для этого, но я предполагаю, что это слишком сложно для замены значения одного элемента, и что это можно легко сделать с помощью sed, чтобы выполнить подстановку с помощью регулярного выражения. Однако я менее чем новичок с этой командой и надеюсь, что какая-нибудь добрая душа, прочитавшая это, сумеет дать мне правильное регулярное выражение для работы.

Одна идея - сделать что-то вроде этого:

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

Может быть, мне лучше просто заменить всю строку файла тем, чем я хочу, поскольку я буду знать имя элемента и новое значение, которое я хочу использовать? Но это предполагает, что рассматриваемый элемент находится в одной строке и что никакие другие данные XML не находятся в той же строке. Я предпочел бы иметь команду, которая в основном заменит значение элемента xyz новой строкой, которую я указываю, и не нужно беспокоиться, если элемент находится в одной строке или нет, и т. Д.

Если sed не лучший инструмент для этой работы, пожалуйста, позвоните мне, чтобы лучше подходить.

Если кто-нибудь может направить меня в правильном направлении, я действительно это ценю, вы, вероятно, сэкономите мне часы проб и ошибок. Заранее спасибо!

- Джеймс

Ответы [ 4 ]

6 голосов
/ 28 августа 2009

sed не будет простым инструментом для многострочных замен. Их можно реализовать с помощью команды N и некоторой рекурсии, проверяя после чтения в каждой строке, было ли найдено закрытие тега ... но это не красиво, и вы никогда не запомните это.

Конечно, на самом деле парсинг xml и замена тегов будет самым безопасным, но если вы знаете, что у вас не возникнет никаких проблем, вы можете попробовать это:

perl -p -0777 -e 's@<xyz>.*?</xyz>@<xyz>new-value</xyz>@sg' <xml-file>

Разбить это:

  • -p говорит ему циклически проходить через ввод и печатать
  • -0777 говорит, что он должен использовать конец файла в качестве разделителя ввода, так что он получает все это в одной записи
  • -e означает, что приходит то, что я хочу, чтобы вы сделали

и сама подстановка:

  • используйте @ в качестве разделителя, чтобы вам не пришлось бежать /
  • используйте *?, не жадную версию, чтобы соответствовать как можно меньшему количеству, поэтому мы не дойдем до последнего появления </xyz> в файле
  • используйте модификатор s, чтобы . соответствовал символам новой строки (чтобы получить значения многострочного тега)
  • использовать модификатор g для многократного совпадения с шаблоном

Тад! Это выводит результат в стандартный вывод - после того, как вы убедитесь, что он делает то, что вам нужно, добавьте параметр -i, чтобы сообщить ему об изменении файла на месте.

4 голосов
/ 29 августа 2009

ОК, поэтому я укусила пулю и заняла время, чтобы написать программу на Java, которая делает то, что я хочу. Ниже приведен оперативный метод, вызываемый моим методом main (), который выполняет эту работу, на случай, если это будет полезно кому-то еще в будущем:

/**
 * 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);
    }
}

Я запускаю программу так:

$ java -cp xmlutility.jar com.abc.util.XmlUtility input.xml output.xml '//name/text()' JAMES
2 голосов
/ 28 августа 2009

Я не хочу быть скептиком, но XML совсем не обычный. Регулярное выражение, вероятно, будет больше проблем, чем оно того стоит. Для получения дополнительной информации см. Здесь: Использование регулярного выражения C # для замены содержимого элемента XML

Ваша мысль о простой Java-программе может быть хорошей в конце концов. Преобразование XSLT может быть проще, если вы хорошо знаете XSLT. Если вы знаете Perl ... это путь ИМХО.

Сказав, что, если вы решите использовать Regex и ваша версия sed поддерживает расширенные регулярные выражения, вы можете сделать его многострочным с помощью / g. Другими словами, поставьте / g в конце регулярного выражения, и оно будет соответствовать вашему шаблону, даже если они в нескольких строках.

Также. предложенное вами регулярное выражение является «жадным». Он захватит самую большую группу символов, какую только может, потому что «. » будет соответствовать от первого вхождения до последнего. Вы можете сделать это "ленивым", изменив подстановочный знак на ". ?". Если после звездочки поставить знак вопроса, он будет соответствовать только одному на.

0 голосов
/ 15 июля 2010

Я пытался сделать то же самое и наткнулся на этот [gu] awk-скрипт, который его достигает.

BEGIN { FS = "[<|>]" }
{
    if ($2 == "xyz") {
        sub($3, "replacement")      
    }
    print
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...