Remove Empty Attributes from XML
I have a buggy xml that co开发者_StackOverflow社区ntains empty attributes and I have a parser that coughs on empty attributes. I have no control over the generation of the xml nor over the parser that coughs on empty attrs. So what I want to do is a pre-processing step that simply removes all empty attributes.
I have managed to find the empty attributes, but now I don't know how to remove them:
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
XPathExpression expr = xpath.compile("//@*");
Object result = expr.evaluate(d, XPathConstants.NODESET);
if (result != null) {
NodeList nodes = (NodeList) result;
for(int node=0;node<nodes.getLength();node++)
{
Node n = nodes.item(node);
if(isEmpty(n.getTextContent()))
{
this.log.warn("Found empty attribute declaration "+n.toString());
NamedNodeMap parentAttrs = n.getParentNode().getAttributes();
parentAttrs.removeNamedItem(n.getNodeName());
}
}
}
This code gives me a NPE when accessing n.getParentNode().getAttributes(). But how can I remove the empty attribute from an element, when I cannot access the element?
If you want to limit it to just the empty attributes, you can use this XPATH:
//*[@*[.='']]
To find attributes that are either empty or that have only whitespace:
//*[@*[normalize-space()='']]
.
That way you select the attributes you want to remove and don't have to loop over every single attribute just to find the empty ones.
The following stylesheet will copy all content in the source document - except attributes that contain only whitespace. The first template simply copies everything - including empty attributes. However, the second template has a higher priority than the first due to its use of a predicate, which is why it will be chosen in preference to the more general first template when an empty attribute is encountered: and this second template does not generate any output.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*[normalize-space()='']"/>
</xsl:stylesheet>
This is probably not the way to do it anyway. Removing something from your NodeList is not going to remove it from the XML. If your parser is actually processing an already loaded DOM and you're manipulating the DOM before the parser gets it something similar to this might work, but it's likely not the best tactic.
You're probably better off preprocessing it by passing it through an XMLFilter on its way to the parser. I located a IBM Developerworks article with sample code that removes all attributes, and it's part of a series that earlier shows how to hook up a chain of filters to your parser.
All this assumes that you're using a SAX parser, but if it's something else, there are likely ways of using SAX and such a filter in a preprocessing step of some sort.
It's also possible you can do the preprocessing by xslt.
getParentNode() doesn't work on attributes.
All nodes, except Attr, Document, DocumentFragment, Entity, and Notation may have a parent.
not 100% sure, but i think you can select all nodes that have an attribute with the following expression:
//*[@*]
you can then easily loop over the attributes and check if they are empty
I would check to make sure that you are actually receiving lists of just nodes of type ATTR, and not Elements, or a mix of the two. I have not used XPathExpression however it may interpret the path "//@*" as "any element with an attribute" as opposed to "all attributes" (what I expect you mean). If the former is true, and your root node has an attribute, it would appear in the resultant nodelist from the query, and by definition [root node].getParentNode() == null producing your NPE.
In addition if you are selecting element nodes and not attr nodes with your query the expression n.getTextContent() would be looking at the text content, not an attribute value (again a likely cause leading to your NPE if the root node is in the list, since most root nodes don't have text content), additionally the attempted removal of the attribute would be a no-op (which you don't really intend anyway).
So if you are receiving element nodes instead of attribute nodes, then you should look at the attribute map and then modify it, and if you have to look at all the attributes, you may be better off just writing a Depth-First-Search looking at the DOM and performing the modifications there.
I actually found a way to do it. Allthough this will not solve the problem perfectly it is O.K. for now. In case of using this, be warned, that it will only catch atributes that have a value that is exactly '' other nonsense like a value only consisting of whitespace will not be caught by this.
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
XPathExpression expr = xpath.compile("//*[@*='']");
Object result = expr.evaluate(d, XPathConstants.NODESET);
if (result != null) {
NodeList nodes = (NodeList) result;
for(int node=0;node<nodes.getLength();node++)
{
Node n = nodes.item(node);
NamedNodeMap attrs = n.getAttributes();
for(int attr=0;attr<attrs.getLength();attr++)
{
Node a = attrs.item(attr);
if(isEmpty(a.getNodeValue()));
{
attrs.removeNamedItem(a.getNodeName());
this.log.warn("Removing empty attribute "+a.toString()+" from element "+n.getNodeName());
}
}
}
}
What a pity regex for comparison is only available as an XSLT extension and not granted to be supported on every XSLT-Processor :-(
精彩评论