Paul - The Programmer

simple & stupid

Auto format XML file

1. Format XML file with VIM.

VIM's auto indent feature supports format the XML files. But generally this feature may be disabled by default. 

To enable the auot indent feature, just add the below options in the .vimrc in user's home directory.

filetype indent on

Next time when the xml file be opened by VIM, the xml file type's indent script will be auotmatically loaded as well. In my environment, the indent script for xml is /usr/share/vim/vim72/ftplugin/xml.vim. The xml.vim's path may vary in different system.

To format the XML contents, just use the vim command in the command mode:

gg=G

This command will reformat the entire contents.

2. Fomart XML file with xmllint.

The xmllint are generally used for validating the xml file syntax. But it also has the ability to format and align the XML documents. The XMLLINT_INDENT environmnet variable controls the indentation. The default value is two spaces ( "  " ).

Basically, open the xml file with VIM then execute the below vim command in command mode:

 

:%!xmllint --format %

This command means the xmllint be excuted with the entire xml document contents as input, then the result of xmllint will be used to update the content of current buffer.

The xmllint also do some refine for the xml document, such as, adding the xml version declaration in the document's head.

XPath error: Cannot select a node here: the context item is an atomic value

Assume the input xml file is like

<people>
    <person>
        <name>paul</name>
        <gender>male</gender>
    </person>
    <person>
        <name>lee</name>
        <gender>female</gender>
    </person>
</people>

This XPath error complained for the below style sheet. 

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:element name="team">
                <xsl:for-each select="distinct-values(//gender)">
                    <xsl:variable name="gender" select="."/>
                    <xsl:element name="{$gender}">
                        <xsl:for-each select="//person[gender=$gender]"> <!-- Wrong! A node is selected for actomic context item. -->
                            <xsl:variable name="name" select="name"/>
                            <xsl:element name="{$name}"/> 
                        </xsl:for-each>
                    </xsl:element>
                </xsl:for-each>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

This error means that a node was selected for a actomic context item. The atomic context item is chosen by the outer for-each loop <xsl:for-each select="distinct-values(//gender)"/>.

Node items can not be selected in this loop context.

The solution is define a variable outside of loop. the variable value is the string of the node path. Then select the node with that variable.

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:element name="team">
            <xsl:variable name="person" select="//person"/> <!-- Define the variable with the node path. -->
                <xsl:for-each select="distinct-values(//gender)">
                    <xsl:variable name="gender" select="."/>
                    <xsl:element name="{$gender}">
                        <xsl:for-each select="$person[gender=$gender]"> <!-- Select the node by the variable defined outside of this for-each loop. -->
                            <xsl:variable name="name" select="name"/>
                            <xsl:element name="{$name}"/> 
                        </xsl:for-each>
                    </xsl:element>
                </xsl:for-each>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

Weird enough, but seems like the only available solution. 

Remove empty lines by VIM global command

The VIM global command can select the lines that match a certain pattern in the scope of whole file. And the appened commands can operate on the selected lines.

Either the following command can be used to remove the empty lines.

:g/^\(\t\|\s\)*$/d
:v/\S/d

The command ':v' is similar to the command 'grep -v' which select the inverted matched lines.

\S is the meta character for the characters which is not white space or tab.

Alter the word order in lines by VIM regular expression

Somehow, I am required to alter the order of word in lines in a text file.

For example, the input text file is like below.

hello world
hello paul
this  is
for   test

I want this file be updated was below.

world hello
paul hello
is  this
test   for

Thanks to regular expression, I do not need to update the lines one after another.

The vim regular expression is a little different than the Perl's. But most of syntax are similar.

\w  : word charaters, includes both alphabet and number characters.

\s   : space characters.

*   : match zero or more preceding character or meta character like \s, \w.

The patterns can be grouped by enclosing with \( and \). 

& :  the whole matched pattern

\0:   the whole matched pattern

\1:   the matched pattern in the first pair of \( and \)

\2:   the matched pattern in the second pair of \( and \)

So, to accomplish the above task, the substitution regular expression is :

:%s/\(\w*\)\(\s*\)\(\w*\)/\3\2\1/

 

The proper way to access to file in resource by Java

The below code demonstrates how to access to a resource file.

import java.io.*;

public class FileTest{
    public void test() throws IOException {
        String filePath = this.getClass().getResource("/test.txt").getPath();
        System.out.println( filePath );
        System.out.println( file.exists() );
    }

    public static void main(String [] args) throws Exception {
       FileTest test = new FileTest();
       test.test();
    }
}

Try to execute:

~/lab/test$ java FileTest 
/home/paul/lab/test/test.txt
true

It seems work perfectly. But this code is acturally not 100% right. When the class file is executed in a directory which name contains space character, the resource file will not be found.

~/lab/my test$ java FileTest 
/home/paul/lab/my%20test/test.txt
false

The space character in the file path was replaced by the '%20'. So, I update the path string before use it to create the File object.

import java.io.*;

public class FileTest{
    public void test() throws IOException {
        String filePath = this.getClass().getResource("/test.txt").getPath();
        System.out.println( filePath.replace("%20", " ") );
        File file = new File(filePath.replace("%20", " "));
        System.out.println( file.exists() );
    }
    public static void main(String [] args) throws Exception {
       FileTest test = new FileTest();
       test.test();
    }
}

Try this again:

~/lab/my test$ java FileTest 
/home/paul/lab/my test/test.txt
true

Looks like it's all right this time. Even the space charater in pah is supported now.

But this is still not the best way.

The above code acturally use the URL to access to the file. But the best way is use URI to access to file. Only the URI can directly support both the space and Chinese characters in file path.

import java.io.*;

public class FileTest{
    public void test() throws IOException, URISyntaxException {
        System.out.println( this.getClass().getResource("/test.txt").toURI( ).getPath());
        File file = new File(this.getClass().getResource("/test.txt").toURI());
        System.out.println( file.exists() );
    }
    public static void main(String [] args) throws Exception {
       FileTest test = new FileTest();
       test.test();
    }
}

The execution result:

~/lab/my test$ java FileTest
/home/paul/lab/my test/test.txt
true 

It works properly and the code is more simple with the URI.

The above code does not support to access to the files in another Jar file's resouce. But I'd like to talk about this topic in another post.  ;-)