dom4j解析国际化(xml:lang)XML文件

1、问题背景

由于老项目需要进行国际化(翻译英文),其中一些xml内容也需要进行翻译。但这时问题就来了,英文版是有了中文怎么办?

存两个xml?abc.xml,abc_zh_CN.xml。这也是个方法但这就需要修改读写xml的模块,让它像properties一样可以支持按语言读取。不想存2份xml的另一个原因就是并不是xml中的所有内容都需要翻译,这样的方式无疑需要维护很多重复配置。

2、分析解决

xml本身就支持多语言,可以采用xml:lang属性来完成。dom4j是不是也可以按xml:lang来解析?

a、首先查看了dom4j的api,发现有一个XMLFilter这样的类,以这个为突破口。

b、需要解析的xml样本(/org/noahx/xmli18n/test.xml)

<?xml version="1.0" encoding="UTF-8"?>

<root>

    <test xml:lang="zh">
        <abc>你好0</abc>
        <bcd bye="再见0"/>
        <test>嵌套测试</test>
    </test>


    <test xml:lang="en">
        <abc>hello0</abc>
        <bcd bye="goodbye0"/>
        <test>嵌套测试2</test>
    </test>

    <test>
        <abc>你好1</abc>
        <abc xml:lang="zh">你好2</abc>
        <abc xml:lang="en">hello1</abc>
        <bcd bye="goodbye1" xml:lang="en"/>
        <bcd bye="再见1"/>
        <bcd bye="再见2" xml:lang="zh_CN"/>
        <test xml:lang="en">嵌套测试3
        </test>
        <test>嵌套测试4
        </test>
        <test>
            <abc xml:lang="en">hello2</abc>
            <bcd xml:lang="en" bye="goodbye2"/>
            <abc xml:lang="zh">你好3</abc>
            <abc xml:lang="zh_TW">你好4</abc>
            <abc xml:lang="zh_CN">你好5</abc>
        </test>
    </test>


</root>
一般来说在相对大的节点定义一个xml:lang=就可以了,就像上面的test节点。


c、开发LocaleXMLFilter过滤掉不符合的Locale(org.noahx.xmli18n.LocaleXMLFilter)

package org.noahx.xmli18n;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.XMLFilterImpl;

import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created with IntelliJ IDEA.
 * User: noah
 * Date: 10/29/12
 * Time: 10:37 AM
 * To change this template use File | Settings | File Templates.
 */
public class LocaleXMLFilter extends XMLFilterImpl {

    /**
     * Locale正则式
     */
    private static final Pattern LOCALE_PATTERN =
            Pattern.compile("(^[^_-]*)(?:[_-]([^_-]*)(?:[_-]([^_-]*))?)?");

    /**
     * 默认读取XML使用的Locale
     */
    private Locale defaultLocale;


    /**
     * 存放当前xml元素路径
     */
    private StringBuilder currentPath = new StringBuilder("#");

    /**
     * 存放忽略元素路径
     */
    private Set<String> ignoreSet = new HashSet<String>();


    public LocaleXMLFilter(Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    /**
     * 起始元素过滤
     *
     * @param url
     * @param localName
     * @param qName
     * @param att
     * @throws SAXException
     */
    public void startElement(String url, String localName,
                             String qName, Attributes att) throws SAXException {

        boolean parentIgnoring = isIgnoreNode();        //判断父节点是否已经被忽略

        currentPath.append(localName);                //生成xml路径,# => #root/,#root/=>#root/a/
        currentPath.append("/");

        boolean ignoring = parentIgnoring;   //子节点顺延父节点忽略

        if (!ignoring) {                      //判断xml:lang是否与defaultLocale冲突,如果不一样,忽略
            String lang = att.getValue("xml:lang");
            if (lang != null) {
                Locale xmlLocale = getLocaleFromLocaleString(lang);
                if (notSameLocale(xmlLocale)) {
                    ignoring = true;
                }

            }
        }

        if (ignoring) {     //忽略
            tagIgnoreNode();
        } else {            //不忽略
            super.startElement(url, localName, qName, att);
        }
    }

    /**
     * 中间字符过滤
     *
     * @param data
     * @param start
     * @param length
     * @throws SAXException
     */
    public void characters(char[] data, int start, int length)
            throws SAXException {
        if (!isIgnoreNode()) {      //不忽略
            super.characters(data, start, length);
        }
    }

    /**
     * 结束元素过滤
     *
     * @param url
     * @param localName
     * @param qName
     * @throws SAXException
     */
    public void endElement(String url, String localName, String qName)
            throws SAXException {


        if (isIgnoreNode()) {     //忽略
            untagIgnoreNode();
        } else {     //不忽略
            super.endElement(url, localName, qName);
        }

        currentPath.replace(currentPath.length() - localName.length() - 1, currentPath.length(), "");  //清除当前路径,#/root/a/ => #/root/


    }

    /**
     * 判断是否属于同语言,同国家
     *
     * @param xmlLocale
     * @return
     */
    private boolean notSameLocale(Locale xmlLocale) {
        boolean same = true;

        if (xmlLocale.getLanguage().equals(defaultLocale.getLanguage())) {  //same lang
            if (!xmlLocale.getCountry().equals("")) {
                if (xmlLocale.getCountry().equals(defaultLocale.getCountry())) {       //same country

                    if (!xmlLocale.getVariant().equals("") && !xmlLocale.getVariant().equals(defaultLocale.getVariant())) {   //diff variant
                        same = false;
                    }
                } else {
                    same = false;
                }
            }

        } else {
            same = false;
        }
        return !same;
    }

    /**
     * zh_CN字符串转换为Locale
     *
     * @param s
     * @return
     */
    private Locale getLocaleFromLocaleString(String s) {
        if (s == null) {
            return null;
        }

        Matcher matcher = LOCALE_PATTERN.matcher(s);

        matcher.find();

        String language = matcher.group(1);
        language = (language == null) ? "" : language;
        String country = matcher.group(2);
        country = (country == null) ? "" : country;
        String variant = matcher.group(3);
        variant = (variant == null) ? "" : variant;

        return new Locale(language, country, variant);
    }

    /**
     * 当前节点是否被忽略
     *
     * @return
     */
    private boolean isIgnoreNode() {
        return ignoreSet.contains(currentPath.toString());
    }

    /**
     * 标记为忽略
     */
    private void tagIgnoreNode() {
        ignoreSet.add(currentPath.toString());
    }

    /**
     * 撤销标记为忽略
     */
    private void untagIgnoreNode() {
        ignoreSet.remove(currentPath.toString());
    }


}
方式就是在startElement,characters,endElement时进行干预从根本上过滤掉不符合的Locale内容。这样就可以把对dom4j的影响降低到最小。


d、主程序测试(org.noahx.xmli18n.TestLangXml)

package org.noahx.xmli18n;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.util.List;
import java.util.Locale;

/**
 * Created with IntelliJ IDEA.
 * User: noah
 * Date: 10/26/12
 * Time: 5:19 PM
 * To change this template use File | Settings | File Templates.
 */
public class TestLangXml {

    public static void main(String[] args) {
        SAXReader saxReader = new SAXReader();
        saxReader.setXMLFilter(new LocaleXMLFilter(Locale.SIMPLIFIED_CHINESE));

        try {
            Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("org/noahx/xmli18n/test.xml"));


            List<Node> nodes = document.selectNodes("//root/test");


            for (Node n : nodes) {
                System.out.println(n.asXML());
            }
            System.out.println(nodes.size());

        } catch (DocumentException e) {
            e.printStackTrace();
        }

    }
}

从打印的xml的内容中就可以看到,不符合的内容已经被过滤。我们对dom4j只是加入saxReader.setXMLFilter(new LocaleXMLFilter(Locale.SIMPLIFIED_CHINESE));这一行。

3、总结

这个方法对原有的程序改动较小,符合我的需要。

源代码下载:http://sdrv.ms/Tpr6Op

你可能感兴趣的:(xml,dom4j,国际化,多语言,i18n)