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