如何使用 SAX 解析器删除 xml 命名空间
how to remove xml namesapce with SAX parser
我需要使用 SAX 解析器进行 xml 转换,为此我需要从 xml 中删除命名空间。由于我们正在处理巨大的 xml,我需要使用 SAX 解析器。
样本输入xml:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns2="http://www.google.com/generation/type">
<ns2:meta>
<gender xmlns="" xmlns:ns5="http://www.google.com/generation">M</gender>
<dateOfBirth xmlns="" xmlns:ns5="http://www.google.com/generation">1976-07-19</dateOfBirth>
<ns2:languageRef>ENG</ns2:languageRef>
</ns2:meta>
<root>
借助 SAX 解析器,我需要以下输出。
<root>
<meta>
<gender>M</gender>
<dateOfBirth>1976-07-19</dateOfBirth>
<languageRef>ENG</languageRef>
</ns2:meta>
<root>
提前致谢..
我试过的代码,
我尝试使用 XMLFlterImpl,
XMLReader xr = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (qName.contains(":")) {
String[] data = data = qName.split(":");
super.startElement(uri, localName, data[1], atts);
} else {
super.startElement(uri, localName, qName, atts);
}
}
这会删除元素名称前缀(命名空间),但不确定如何删除命名空间属性
通常尝试删除命名空间以执行 "transformation" 是对如何处理 XML 缺乏理解的标志,但通常如果您使用 SAX 并希望更改已处理的 XML 你可以实现一个过滤器 https://docs.oracle.com/javase/8/docs/api/org/xml/sax/XMLFilter.html, starting with https://docs.oracle.com/javase/8/docs/api/org/xml/sax/helpers/XMLFilterImpl.html 作为一个基类,并覆盖你期望和想要剥离命名空间的方法。
编辑:
好的,根据@MichaelKay 的评论指导,这是我更新的答案。
要从标签中删除名称空间:
正如他在回答中所建议的那样,startElement
应该用 ""
代替 uri
。 结束标签呢?:
在你的问题中,我不明白为什么你想要 ns2
作为结束 meta
标签,特别是当你想为起始标签删除它时。
我假设您也希望将其从结束标记中删除。所以类似地 endElement
也应该用 ""
代替 uri
。
过滤 XMLNS 属性:
您可以创建一个新的 AttributesImpl
。然后检查属性列表并检查 QName
是否以 xmlns
开头,如果不是,则将其添加到 AttributesImpl
并在 startElement
中使用它作为 :
super.startElement("", localName, data[1], aImpl);
另请注意,根据@MartinHonnen,是的,属性的 uri
也应为“”,并且 qName
应与元素一样没有前缀。但是如果你想保留属性的名称(我认为你不想这样做)你可以保持 atts.getQName(i)
不变。
同时将命名空间功能设置为 false,如:
xf.setFeature("http://xml.org/sax/features/namespaces", false);
代码:
try {
InputSource file = new InputSource("filterns.xml");
XMLFilterImpl xf = new XMLFilterImpl(
XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(String uri, String localName,
String qName, Attributes atts) throws SAXException {
AttributesImpl aImpl = new AttributesImpl();
int l = atts.getLength();
for (int i = 0; i < l; i++) {
if (atts.getQName(i) != null
&& atts.getQName(i).startsWith("xmlns")) {
continue;
} else {
String aQName = atts.getQName(i);
String[] s = aQName.split(":");
if (s.length > 1) {
aQName = s[1];
}
aImpl.addAttribute("",
atts.getLocalName(i), aQName,
atts.getType(i), atts.getValue(i));
}
}
String[] s = qName.split(":");
if (s.length > 1) {
super.startElement("", localName, s[1], aImpl);
} else {
super.startElement("", localName, qName, aImpl);
}
}
@Override
public void endElement(String uri, String localName,
String qName) throws SAXException {
String[] s = qName.split(":");
if (s.length > 1) {
super.endElement("", localName, s[1]);
} else {
super.endElement("", localName, qName);
}
}
@Override
public void startPrefixMapping(String prefix, String uri) {
}
};
xf.setFeature("http://xml.org/sax/features/namespaces", false);
SAXSource src = new SAXSource(xf, file);
StringWriter stringWriter = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory
.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
transformer.transform(src, new StreamResult(stringWriter));
String xml = stringWriter.toString();
System.out.println(xml);
} catch (Exception e) {
e.printStackTrace();
}
在此代码中:
super.startElement(uri, localName, data[1], atts);
您正在将未更改的原始命名空间 URI 传递给输出。你需要摆脱它,使用:
super.startElement("", localName, data[1], atts);
这是使用 VTD-XML 可以完成的示例。如果有任何问题,请告诉我。
import com.ximpleware.*;
import java.io.*;
public class removeNS {
public static void main(String[] args) throws VTDException, IOException{
// TODO Auto-generated method stub
VTDGen vg = new VTDGen();
if (!vg.parseFile("d:\xml\ns.xml", true))
return;
VTDNav vn = vg.getNav();
XMLModifier xm = new XMLModifier(vn);
for (int i=0;i<vn.getTokenCount();i++){
int t = vn.getTokenType(i);
switch(t){
case VTDGen.TOKEN_STARTING_TAG:
stripElementPrefix(i,vn,xm);
break;
case VTDGen.TOKEN_ATTR_NAME:
stripAttrPrefix(i,vn,xm);
break;
case VTDGen.TOKEN_ATTR_NS:
xm.removeAttribute(i);
default:
}
}
xm.output("d:\xml\nsOut.xml");
}
public static void stripAttrPrefix(int i, VTDNav vn, XMLModifier xm) throws VTDException{
//get the offset and length of localname part of starting tag
int os1 = vn.getTokenOffset(i);
int len = vn.getTokenLength(i);
if ((len>>16)!=0){
int temp1 = (0xffff & len) - (len>>16)-1;
int temp2 = os1 + (temp1);
xm.removeContent(temp1, temp2);
}
//int offset=
}
public static void stripElementPrefix(int i, VTDNav vn, XMLModifier xm) throws VTDException, UnsupportedEncodingException{
//int os1 = vn.getTokenOffset(i)
int os1 = vn.getTokenOffset(i);
int len = vn.getTokenLength(i);
if ((len>>16)!=0){
int temp1 = (0xffff & len) - (len>>16)-1;
int temp2 = os1 + (len>>16)+1;
String s = vn.toRawString(temp2, temp1);
System.out.println(s);
vn.recoverNode(i);
xm.updateElementName(s);
}
}
}
我需要使用 SAX 解析器进行 xml 转换,为此我需要从 xml 中删除命名空间。由于我们正在处理巨大的 xml,我需要使用 SAX 解析器。
样本输入xml:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:ns2="http://www.google.com/generation/type">
<ns2:meta>
<gender xmlns="" xmlns:ns5="http://www.google.com/generation">M</gender>
<dateOfBirth xmlns="" xmlns:ns5="http://www.google.com/generation">1976-07-19</dateOfBirth>
<ns2:languageRef>ENG</ns2:languageRef>
</ns2:meta>
<root>
借助 SAX 解析器,我需要以下输出。
<root>
<meta>
<gender>M</gender>
<dateOfBirth>1976-07-19</dateOfBirth>
<languageRef>ENG</languageRef>
</ns2:meta>
<root>
提前致谢..
我试过的代码,
我尝试使用 XMLFlterImpl,
XMLReader xr = new XMLFilterImpl(XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (qName.contains(":")) {
String[] data = data = qName.split(":");
super.startElement(uri, localName, data[1], atts);
} else {
super.startElement(uri, localName, qName, atts);
}
}
这会删除元素名称前缀(命名空间),但不确定如何删除命名空间属性
通常尝试删除命名空间以执行 "transformation" 是对如何处理 XML 缺乏理解的标志,但通常如果您使用 SAX 并希望更改已处理的 XML 你可以实现一个过滤器 https://docs.oracle.com/javase/8/docs/api/org/xml/sax/XMLFilter.html, starting with https://docs.oracle.com/javase/8/docs/api/org/xml/sax/helpers/XMLFilterImpl.html 作为一个基类,并覆盖你期望和想要剥离命名空间的方法。
编辑:
好的,根据@MichaelKay 的评论指导,这是我更新的答案。
要从标签中删除名称空间:
正如他在回答中所建议的那样,startElement
应该用 ""
代替 uri
。 结束标签呢?:
在你的问题中,我不明白为什么你想要 ns2
作为结束 meta
标签,特别是当你想为起始标签删除它时。
我假设您也希望将其从结束标记中删除。所以类似地 endElement
也应该用 ""
代替 uri
。
过滤 XMLNS 属性:
您可以创建一个新的 AttributesImpl
。然后检查属性列表并检查 QName
是否以 xmlns
开头,如果不是,则将其添加到 AttributesImpl
并在 startElement
中使用它作为 :
super.startElement("", localName, data[1], aImpl);
另请注意,根据@MartinHonnen,是的,属性的 uri
也应为“”,并且 qName
应与元素一样没有前缀。但是如果你想保留属性的名称(我认为你不想这样做)你可以保持 atts.getQName(i)
不变。
同时将命名空间功能设置为 false,如:
xf.setFeature("http://xml.org/sax/features/namespaces", false);
代码:
try {
InputSource file = new InputSource("filterns.xml");
XMLFilterImpl xf = new XMLFilterImpl(
XMLReaderFactory.createXMLReader()) {
@Override
public void startElement(String uri, String localName,
String qName, Attributes atts) throws SAXException {
AttributesImpl aImpl = new AttributesImpl();
int l = atts.getLength();
for (int i = 0; i < l; i++) {
if (atts.getQName(i) != null
&& atts.getQName(i).startsWith("xmlns")) {
continue;
} else {
String aQName = atts.getQName(i);
String[] s = aQName.split(":");
if (s.length > 1) {
aQName = s[1];
}
aImpl.addAttribute("",
atts.getLocalName(i), aQName,
atts.getType(i), atts.getValue(i));
}
}
String[] s = qName.split(":");
if (s.length > 1) {
super.startElement("", localName, s[1], aImpl);
} else {
super.startElement("", localName, qName, aImpl);
}
}
@Override
public void endElement(String uri, String localName,
String qName) throws SAXException {
String[] s = qName.split(":");
if (s.length > 1) {
super.endElement("", localName, s[1]);
} else {
super.endElement("", localName, qName);
}
}
@Override
public void startPrefixMapping(String prefix, String uri) {
}
};
xf.setFeature("http://xml.org/sax/features/namespaces", false);
SAXSource src = new SAXSource(xf, file);
StringWriter stringWriter = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory
.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
transformer.transform(src, new StreamResult(stringWriter));
String xml = stringWriter.toString();
System.out.println(xml);
} catch (Exception e) {
e.printStackTrace();
}
在此代码中:
super.startElement(uri, localName, data[1], atts);
您正在将未更改的原始命名空间 URI 传递给输出。你需要摆脱它,使用:
super.startElement("", localName, data[1], atts);
这是使用 VTD-XML 可以完成的示例。如果有任何问题,请告诉我。
import com.ximpleware.*;
import java.io.*;
public class removeNS {
public static void main(String[] args) throws VTDException, IOException{
// TODO Auto-generated method stub
VTDGen vg = new VTDGen();
if (!vg.parseFile("d:\xml\ns.xml", true))
return;
VTDNav vn = vg.getNav();
XMLModifier xm = new XMLModifier(vn);
for (int i=0;i<vn.getTokenCount();i++){
int t = vn.getTokenType(i);
switch(t){
case VTDGen.TOKEN_STARTING_TAG:
stripElementPrefix(i,vn,xm);
break;
case VTDGen.TOKEN_ATTR_NAME:
stripAttrPrefix(i,vn,xm);
break;
case VTDGen.TOKEN_ATTR_NS:
xm.removeAttribute(i);
default:
}
}
xm.output("d:\xml\nsOut.xml");
}
public static void stripAttrPrefix(int i, VTDNav vn, XMLModifier xm) throws VTDException{
//get the offset and length of localname part of starting tag
int os1 = vn.getTokenOffset(i);
int len = vn.getTokenLength(i);
if ((len>>16)!=0){
int temp1 = (0xffff & len) - (len>>16)-1;
int temp2 = os1 + (temp1);
xm.removeContent(temp1, temp2);
}
//int offset=
}
public static void stripElementPrefix(int i, VTDNav vn, XMLModifier xm) throws VTDException, UnsupportedEncodingException{
//int os1 = vn.getTokenOffset(i)
int os1 = vn.getTokenOffset(i);
int len = vn.getTokenLength(i);
if ((len>>16)!=0){
int temp1 = (0xffff & len) - (len>>16)-1;
int temp2 = os1 + (len>>16)+1;
String s = vn.toRawString(temp2, temp1);
System.out.println(s);
vn.recoverNode(i);
xm.updateElementName(s);
}
}
}