2

I have an XML file with a bunch of data contained by custom tags. This is all useful for one project I have, but for another project I don't need so much info. So I'd like to trim the XML file, and get rid of all instances of certain tags and whatever is between the tags.

<GOBJ>
    <cost>4</cost>
    <duration>n/a</duration>
    <item>Stone Block</item>
    <type>Construction - Material</type>
    <misc>Use these blocks to build things. These blocks don't degrade.</misc>
</GOBJ>

I only want to keep [item]blah[item] and [type]blah[type], the rest should be deleted/removed.

Later on, I will need to check the text of [type] and replace its contents if it matches certain words. For example, if the word metal is anywhere within the [type] tag, then replace the contents of that tag with just the word metal.

I know this is a big request; I appreciate any help.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
C_K
  • 1,243
  • 4
  • 18
  • 29
  • 9
    Why regex and not an XML parser? It doesn't make sense. Regex is a very bad choice for manipulating XML. – spender Jun 12 '11 at 03:01
  • 2
    Is it guaranteed that the xml will look like this? -- if its one tag one line then it should be dead easy enough to use sed or even grep to remove the unneeded tags. – Sai Jun 12 '11 at 03:06
  • @spender - not sure on the terminology for xml. Parser, regex, whatever if it does what i need. – C_K Jun 12 '11 at 03:06
  • @Sai - What's contained by the tags is different for each entry. If it's still easy to do, then how so? – C_K Jun 12 '11 at 03:07
  • 5
    Please take the time to read through the related questions on the lower-right of the page. Using regex on simple XML is doable, but the problem quickly grows out of control as the complexity of the file changes. For robustness and maintainability a good parser is recommended. You don't say what language you favor, but all major languages have parsers that are up to the task. Because you are removing tags, I'd look into a scrubber or white-list program. You say what tags you want to keep and they'll throw away everything else. – the Tin Man Jun 12 '11 at 03:12
  • The best answer is, http://stackoverflow.com/a/1732454/135078 (Beware Zalgo) – Kelly S. French Jan 12 '12 at 22:37

5 Answers5

2

Another way is to just use simple XML → XML (XSLT 1.0 with XPath 1.0) transformation like below. It's easy to adapt for your requirements and reuse for other documents.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="root">
        <root>
            <xsl:apply-templates select="GOBJ"/>
        </root>
    </xsl:template>

    <xsl:template match="GOBJ">
        <GOBJ>
            <xsl:copy-of select="item"/>
            <type>
                <xsl:choose>
                    <xsl:when test="contains(type, 'metal')">
                        <xsl:text>metal</xsl:text>
                    </xsl:when>
                    <!-- other xsl:when conditions here -->
                    <xsl:otherwise>
                        <xsl:value-of select="type"/>
                    </xsl:otherwise>
                </xsl:choose>
            </type>
        </GOBJ>
    </xsl:template>
</xsl:stylesheet>

I know it's not regex based solution, but IMHO it's better to use native XML-oriented toolkit.

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
0

If you want to parse XML log file so you can do with regex {java}, <[^<]+<.so you get <name>DEV</name>. Output like name>DEV.

0

Assuming that the file is laid out exactly as your example, multiplied by as many records as required, and that you wish to preserve the original layout as much as possible, replacing

(<GOBJ>[^<]+?).+?(<item>.+?<\/type>\n).+?(<\/GOBJ>)

with

$1$2$3

globally and the regex is set to operate in 'singleline' mode, will do what you require iff, element <GOBJ> is uppercase, other elements are in lowercase, there is ever only one instance of each element per record, and element <item> always appears immediately before element <type> in each record.

In JavaScript, this would be:

var result = src.replace(
    /(<GOBJ>[^<]+?).+?(<item>.+?<\/type>\n).+?(<\/GOBJ>)/g, 
    '$1$2$3'
);

Note that the strict conditions alleviate any issues related to parsing XML using a regular expression. If the conditions cannot be met, you would be far better served using an XML-specific tool, like XSLT.

Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
0

Here's a grep solution: grep -E '(<item>|<type>)' myfile.xml

Bohemian
  • 412,405
  • 93
  • 575
  • 722
0

I developed another way to tackle the problem; I built a jquery script that split up the xml code (i replaced all the left/right arrows with a different symbol before hand), and output the array entry if i didn't contain another certain symbol.

var name = $('div').text().trim().split(/\[name\](.*?)\[\/name\]/g);
var type = $('div').text().trim().split(/\[type\](.*?)\[\/type\]/g);
for (i = 0; name.length > i; i++) {
        if ((type[i].match(/\[/g))) {
            type[i] = "";
        }
        if (!(name[i].match(/\[/g))) {
            if (type[i].match(/construction/g)) {type[i] = "T_C";}
            if (type[i].match(/material/g)) {type[i] = "T_M";}
            if (type[i].match(/metalwork/g)) {type[i] = "T_W";}
            if (type[i].match(/water/g)) {type[i] = "T_W";}
            if (type[i].match(/oil/g)) {type[i] = "T_O";}
            if (type[i].match(/precious/g)) {type[i] = "T_P";}
            if (type[i].match(/magic/g)) {type[i] = "T_M";}
            $('.Collect').append('<p>a href="../Img/XXX/' + name[i] + '.jpg" class="' + type[i] + '">' + name[i] + '/a></p>');
        } else {
            name[i] = "";
        }

    }

The output is formatted that way so that i can just copy paste the page into a txt/html file, and have it pretty much as i wanted it. I'll have to figure out some way to replace XXX with the appropriate Directory name...

I only needed to do this once or twice, so pure automation wasn't imperative.

C_K
  • 1,243
  • 4
  • 18
  • 29