First Commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
HtmlCleaner is a project originally developed by Vladimir Nikic (http://htmlcleaner.sourceforge.net/).
|
||||
|
||||
This version is modified by Zheng Sun.
|
||||
@@ -0,0 +1,227 @@
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 69
|
||||
/svnroot/htmlcleaner/!svn/ver/182/trunk/src/main/java/org/htmlcleaner
|
||||
END
|
||||
Utils.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 80
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/Utils.java
|
||||
END
|
||||
PrettyXmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 94
|
||||
/svnroot/htmlcleaner/!svn/ver/179/trunk/src/main/java/org/htmlcleaner/PrettyXmlSerializer.java
|
||||
END
|
||||
SimpleXmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 94
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/SimpleXmlSerializer.java
|
||||
END
|
||||
BrowserCompactXmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 102
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/BrowserCompactXmlSerializer.java
|
||||
END
|
||||
HtmlCleanerForAnt.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 92
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/HtmlCleanerForAnt.java
|
||||
END
|
||||
CommentNode.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/CommentNode.java
|
||||
END
|
||||
ITagInfoProvider.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 91
|
||||
/svnroot/htmlcleaner/!svn/ver/179/trunk/src/main/java/org/htmlcleaner/ITagInfoProvider.java
|
||||
END
|
||||
BaseToken.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 84
|
||||
/svnroot/htmlcleaner/!svn/ver/179/trunk/src/main/java/org/htmlcleaner/BaseToken.java
|
||||
END
|
||||
TagNode.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 82
|
||||
/svnroot/htmlcleaner/!svn/ver/181/trunk/src/main/java/org/htmlcleaner/TagNode.java
|
||||
END
|
||||
CleanerTransformations.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 97
|
||||
/svnroot/htmlcleaner/!svn/ver/143/trunk/src/main/java/org/htmlcleaner/CleanerTransformations.java
|
||||
END
|
||||
XmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 88
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/XmlSerializer.java
|
||||
END
|
||||
DefaultTagProvider.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 93
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/DefaultTagProvider.java
|
||||
END
|
||||
HtmlTokenizer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 88
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/HtmlTokenizer.java
|
||||
END
|
||||
TagNodeVisitor.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 89
|
||||
/svnroot/htmlcleaner/!svn/ver/152/trunk/src/main/java/org/htmlcleaner/TagNodeVisitor.java
|
||||
END
|
||||
CompactXmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 95
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/CompactXmlSerializer.java
|
||||
END
|
||||
TagTransformation.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 92
|
||||
/svnroot/htmlcleaner/!svn/ver/143/trunk/src/main/java/org/htmlcleaner/TagTransformation.java
|
||||
END
|
||||
EndTagToken.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svnroot/htmlcleaner/!svn/ver/179/trunk/src/main/java/org/htmlcleaner/EndTagToken.java
|
||||
END
|
||||
CompactHtmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 96
|
||||
/svnroot/htmlcleaner/!svn/ver/156/trunk/src/main/java/org/htmlcleaner/CompactHtmlSerializer.java
|
||||
END
|
||||
PrettyHtmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 95
|
||||
/svnroot/htmlcleaner/!svn/ver/161/trunk/src/main/java/org/htmlcleaner/PrettyHtmlSerializer.java
|
||||
END
|
||||
SimpleHtmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 95
|
||||
/svnroot/htmlcleaner/!svn/ver/156/trunk/src/main/java/org/htmlcleaner/SimpleHtmlSerializer.java
|
||||
END
|
||||
DoctypeToken.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 87
|
||||
/svnroot/htmlcleaner/!svn/ver/179/trunk/src/main/java/org/htmlcleaner/DoctypeToken.java
|
||||
END
|
||||
Serializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 85
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/Serializer.java
|
||||
END
|
||||
DomSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 88
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/DomSerializer.java
|
||||
END
|
||||
HtmlNode.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 83
|
||||
/svnroot/htmlcleaner/!svn/ver/152/trunk/src/main/java/org/htmlcleaner/HtmlNode.java
|
||||
END
|
||||
XPather.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 82
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/XPather.java
|
||||
END
|
||||
TagToken.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 83
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/TagToken.java
|
||||
END
|
||||
ConfigFileTagProvider.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 96
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/ConfigFileTagProvider.java
|
||||
END
|
||||
SpecialEntity.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 88
|
||||
/svnroot/htmlcleaner/!svn/ver/143/trunk/src/main/java/org/htmlcleaner/SpecialEntity.java
|
||||
END
|
||||
HtmlCleanerException.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 95
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/HtmlCleanerException.java
|
||||
END
|
||||
ContentNode.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/ContentNode.java
|
||||
END
|
||||
JDomSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 89
|
||||
/svnroot/htmlcleaner/!svn/ver/182/trunk/src/main/java/org/htmlcleaner/JDomSerializer.java
|
||||
END
|
||||
CleanerProperties.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 92
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/CleanerProperties.java
|
||||
END
|
||||
CommandLine.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svnroot/htmlcleaner/!svn/ver/180/trunk/src/main/java/org/htmlcleaner/CommandLine.java
|
||||
END
|
||||
TagInfo.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 82
|
||||
/svnroot/htmlcleaner/!svn/ver/179/trunk/src/main/java/org/htmlcleaner/TagInfo.java
|
||||
END
|
||||
XPatherException.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 91
|
||||
/svnroot/htmlcleaner/!svn/ver/178/trunk/src/main/java/org/htmlcleaner/XPatherException.java
|
||||
END
|
||||
HtmlSerializer.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 89
|
||||
/svnroot/htmlcleaner/!svn/ver/161/trunk/src/main/java/org/htmlcleaner/HtmlSerializer.java
|
||||
END
|
||||
HtmlCleaner.java
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svnroot/htmlcleaner/!svn/ver/182/trunk/src/main/java/org/htmlcleaner/HtmlCleaner.java
|
||||
END
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Base token interface. Tokens are individual entities recognized by HTML parser.
|
||||
* </p>
|
||||
*/
|
||||
public interface BaseToken {
|
||||
|
||||
public void serialize(Serializer serializer, Writer writer) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Broswer compact XML serializer - creates resulting XML by stripping whitespaces wherever possible,
|
||||
* but preserving single whitespace where at least one exists. This behaviour is well suited
|
||||
* for web-browsers, which usualy treat multiple whitespaces as single one, but make diffrence
|
||||
* between single whitespace and empty text.
|
||||
* </p>
|
||||
*/
|
||||
public class BrowserCompactXmlSerializer extends XmlSerializer {
|
||||
|
||||
public BrowserCompactXmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
List tagChildren = tagNode.getChildren();
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
ListIterator childrenIt = tagChildren.listIterator();
|
||||
while ( childrenIt.hasNext() ) {
|
||||
Object item = childrenIt.next();
|
||||
if (item instanceof ContentNode) {
|
||||
String content = item.toString();
|
||||
boolean startsWithSpace = content.length() > 0 && Character.isWhitespace( content.charAt(0) );
|
||||
boolean endsWithSpace = content.length() > 1 && Character.isWhitespace( content.charAt(content.length() - 1) );
|
||||
content = dontEscape(tagNode) ? content.trim().replaceAll("]]>", "]]>") : escapeXml(content.trim());
|
||||
|
||||
if (startsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
|
||||
if (content.length() != 0) {
|
||||
writer.write(content);
|
||||
if (endsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenIt.hasNext()) {
|
||||
if ( !Utils.isWhitespaceString(childrenIt.next()) ) {
|
||||
writer.write("\n");
|
||||
}
|
||||
childrenIt.previous();
|
||||
}
|
||||
} else if (item instanceof CommentNode) {
|
||||
String content = ((CommentNode) item).getCommentedContent().trim();
|
||||
writer.write(content);
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken)item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* Properties defining cleaner's behaviour
|
||||
*/
|
||||
public class CleanerProperties {
|
||||
|
||||
public static final String BOOL_ATT_SELF = "self";
|
||||
public static final String BOOL_ATT_EMPTY = "empty";
|
||||
public static final String BOOL_ATT_TRUE = "true";
|
||||
|
||||
ITagInfoProvider tagInfoProvider = null;
|
||||
|
||||
boolean advancedXmlEscape = true;
|
||||
boolean transResCharsToNCR = false;
|
||||
boolean useCdataForScriptAndStyle = true;
|
||||
boolean translateSpecialEntities = true;
|
||||
boolean transSpecialEntitiesToNCR = false;
|
||||
boolean recognizeUnicodeChars = true;
|
||||
boolean omitUnknownTags = false;
|
||||
boolean treatUnknownTagsAsContent = false;
|
||||
boolean omitDeprecatedTags = false;
|
||||
boolean treatDeprecatedTagsAsContent = false;
|
||||
boolean omitComments = false;
|
||||
boolean omitXmlDeclaration = false;
|
||||
boolean omitDoctypeDeclaration = true;
|
||||
boolean omitHtmlEnvelope = false;
|
||||
boolean useEmptyElementTags = true;
|
||||
boolean allowMultiWordAttributes = true;
|
||||
boolean allowHtmlInsideAttributes = false;
|
||||
boolean ignoreQuestAndExclam = true;
|
||||
boolean namespacesAware = true;
|
||||
String hyphenReplacementInComment = "=";
|
||||
String pruneTags = null;
|
||||
String booleanAttributeValues = BOOL_ATT_SELF;
|
||||
|
||||
public ITagInfoProvider getTagInfoProvider() {
|
||||
return tagInfoProvider;
|
||||
}
|
||||
|
||||
public boolean isAdvancedXmlEscape() {
|
||||
return advancedXmlEscape;
|
||||
}
|
||||
|
||||
public void setAdvancedXmlEscape(boolean advancedXmlEscape) {
|
||||
this.advancedXmlEscape = advancedXmlEscape;
|
||||
}
|
||||
|
||||
public boolean isTransResCharsToNCR() {
|
||||
return transResCharsToNCR;
|
||||
}
|
||||
|
||||
public void setTransResCharsToNCR(boolean transResCharsToNCR) {
|
||||
this.transResCharsToNCR = transResCharsToNCR;
|
||||
}
|
||||
|
||||
public boolean isUseCdataForScriptAndStyle() {
|
||||
return useCdataForScriptAndStyle;
|
||||
}
|
||||
|
||||
public void setUseCdataForScriptAndStyle(boolean useCdataForScriptAndStyle) {
|
||||
this.useCdataForScriptAndStyle = useCdataForScriptAndStyle;
|
||||
}
|
||||
|
||||
public boolean isTranslateSpecialEntities() {
|
||||
return translateSpecialEntities;
|
||||
}
|
||||
|
||||
public void setTranslateSpecialEntities(boolean translateSpecialEntities) {
|
||||
this.translateSpecialEntities = translateSpecialEntities;
|
||||
}
|
||||
|
||||
public boolean isTransSpecialEntitiesToNCR() {
|
||||
return transSpecialEntitiesToNCR;
|
||||
}
|
||||
|
||||
public void setTransSpecialEntitiesToNCR(boolean transSpecialEntitiesToNCR) {
|
||||
this.transSpecialEntitiesToNCR = transSpecialEntitiesToNCR;
|
||||
}
|
||||
|
||||
public boolean isRecognizeUnicodeChars() {
|
||||
return recognizeUnicodeChars;
|
||||
}
|
||||
|
||||
public void setRecognizeUnicodeChars(boolean recognizeUnicodeChars) {
|
||||
this.recognizeUnicodeChars = recognizeUnicodeChars;
|
||||
}
|
||||
|
||||
public boolean isOmitUnknownTags() {
|
||||
return omitUnknownTags;
|
||||
}
|
||||
|
||||
public void setOmitUnknownTags(boolean omitUnknownTags) {
|
||||
this.omitUnknownTags = omitUnknownTags;
|
||||
}
|
||||
|
||||
public boolean isTreatUnknownTagsAsContent() {
|
||||
return treatUnknownTagsAsContent;
|
||||
}
|
||||
|
||||
public void setTreatUnknownTagsAsContent(boolean treatUnknownTagsAsContent) {
|
||||
this.treatUnknownTagsAsContent = treatUnknownTagsAsContent;
|
||||
}
|
||||
|
||||
public boolean isOmitDeprecatedTags() {
|
||||
return omitDeprecatedTags;
|
||||
}
|
||||
|
||||
public void setOmitDeprecatedTags(boolean omitDeprecatedTags) {
|
||||
this.omitDeprecatedTags = omitDeprecatedTags;
|
||||
}
|
||||
|
||||
public boolean isTreatDeprecatedTagsAsContent() {
|
||||
return treatDeprecatedTagsAsContent;
|
||||
}
|
||||
|
||||
public void setTreatDeprecatedTagsAsContent(boolean treatDeprecatedTagsAsContent) {
|
||||
this.treatDeprecatedTagsAsContent = treatDeprecatedTagsAsContent;
|
||||
}
|
||||
|
||||
public boolean isOmitComments() {
|
||||
return omitComments;
|
||||
}
|
||||
|
||||
public void setOmitComments(boolean omitComments) {
|
||||
this.omitComments = omitComments;
|
||||
}
|
||||
|
||||
public boolean isOmitXmlDeclaration() {
|
||||
return omitXmlDeclaration;
|
||||
}
|
||||
|
||||
public void setOmitXmlDeclaration(boolean omitXmlDeclaration) {
|
||||
this.omitXmlDeclaration = omitXmlDeclaration;
|
||||
}
|
||||
|
||||
public boolean isOmitDoctypeDeclaration() {
|
||||
return omitDoctypeDeclaration;
|
||||
}
|
||||
|
||||
public void setOmitDoctypeDeclaration(boolean omitDoctypeDeclaration) {
|
||||
this.omitDoctypeDeclaration = omitDoctypeDeclaration;
|
||||
}
|
||||
|
||||
public boolean isOmitHtmlEnvelope() {
|
||||
return omitHtmlEnvelope;
|
||||
}
|
||||
|
||||
public void setOmitHtmlEnvelope(boolean omitHtmlEnvelope) {
|
||||
this.omitHtmlEnvelope = omitHtmlEnvelope;
|
||||
}
|
||||
|
||||
public boolean isUseEmptyElementTags() {
|
||||
return useEmptyElementTags;
|
||||
}
|
||||
|
||||
public void setUseEmptyElementTags(boolean useEmptyElementTags) {
|
||||
this.useEmptyElementTags = useEmptyElementTags;
|
||||
}
|
||||
|
||||
public boolean isAllowMultiWordAttributes() {
|
||||
return allowMultiWordAttributes;
|
||||
}
|
||||
|
||||
public void setAllowMultiWordAttributes(boolean allowMultiWordAttributes) {
|
||||
this.allowMultiWordAttributes = allowMultiWordAttributes;
|
||||
}
|
||||
|
||||
public boolean isAllowHtmlInsideAttributes() {
|
||||
return allowHtmlInsideAttributes;
|
||||
}
|
||||
|
||||
public void setAllowHtmlInsideAttributes(boolean allowHtmlInsideAttributes) {
|
||||
this.allowHtmlInsideAttributes = allowHtmlInsideAttributes;
|
||||
}
|
||||
|
||||
public boolean isIgnoreQuestAndExclam() {
|
||||
return ignoreQuestAndExclam;
|
||||
}
|
||||
|
||||
public void setIgnoreQuestAndExclam(boolean ignoreQuestAndExclam) {
|
||||
this.ignoreQuestAndExclam = ignoreQuestAndExclam;
|
||||
}
|
||||
|
||||
public boolean isNamespacesAware() {
|
||||
return namespacesAware;
|
||||
}
|
||||
|
||||
public void setNamespacesAware(boolean namespacesAware) {
|
||||
this.namespacesAware = namespacesAware;
|
||||
}
|
||||
|
||||
public String getHyphenReplacementInComment() {
|
||||
return hyphenReplacementInComment;
|
||||
}
|
||||
|
||||
public void setHyphenReplacementInComment(String hyphenReplacementInComment) {
|
||||
this.hyphenReplacementInComment = hyphenReplacementInComment;
|
||||
}
|
||||
|
||||
public String getPruneTags() {
|
||||
return pruneTags;
|
||||
}
|
||||
|
||||
public void setPruneTags(String pruneTags) {
|
||||
this.pruneTags = pruneTags;
|
||||
}
|
||||
|
||||
public String getBooleanAttributeValues() {
|
||||
return booleanAttributeValues;
|
||||
}
|
||||
|
||||
public void setBooleanAttributeValues(String booleanAttributeValues) {
|
||||
if ( BOOL_ATT_SELF.equalsIgnoreCase(booleanAttributeValues) ||
|
||||
BOOL_ATT_EMPTY.equalsIgnoreCase(booleanAttributeValues) ||
|
||||
BOOL_ATT_TRUE.equalsIgnoreCase(booleanAttributeValues) ) {
|
||||
this.booleanAttributeValues = booleanAttributeValues.toLowerCase();
|
||||
} else {
|
||||
this.booleanAttributeValues = BOOL_ATT_SELF;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Contains transformation collection.
|
||||
*/
|
||||
public class CleanerTransformations {
|
||||
|
||||
private Map mappings = new HashMap();
|
||||
|
||||
/**
|
||||
* Adds specified tag transformation to the collection.
|
||||
* @param tagTransformation
|
||||
*/
|
||||
public void addTransformation(TagTransformation tagTransformation) {
|
||||
if (tagTransformation != null) {
|
||||
mappings.put( tagTransformation.getSourceTag(), tagTransformation );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTransformationForTag(String tagName) {
|
||||
return tagName != null && mappings.containsKey(tagName.toLowerCase());
|
||||
}
|
||||
|
||||
public TagTransformation getTransformation(String tagName) {
|
||||
return tagName != null ? (TagTransformation) mappings.get(tagName.toLowerCase()) : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* <p>Command line usage class.</p>
|
||||
*/
|
||||
public class CommandLine {
|
||||
|
||||
private static String getArgValue(String[] args, String name) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String curr = args[i];
|
||||
int eqIndex = curr.indexOf('=');
|
||||
if (eqIndex >= 0) {
|
||||
String argName = curr.substring(0, eqIndex).trim();
|
||||
String argValue = curr.substring(eqIndex+1).trim();
|
||||
|
||||
if (argName.toLowerCase().startsWith(name.toLowerCase())) {
|
||||
return argValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static boolean toBoolean(String s) {
|
||||
return s != null && ( "on".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s) || "yes".equalsIgnoreCase(s) );
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, XPatherException {
|
||||
String source = getArgValue(args, "src");
|
||||
if ( "".equals(source) ) {
|
||||
System.err.println("Usage: java -jar htmlcleanerXX.jar src = <url | file> [incharset = <charset>] " +
|
||||
"[dest = <file>] [outcharset = <charset>] [taginfofile=<file>] [options...]");
|
||||
System.err.println("");
|
||||
System.err.println("where options include:");
|
||||
System.err.println(" outputtype=simple* | compact | browser-compact | pretty | htmlsimple | htmlcompact | htmlpretty");
|
||||
System.err.println(" advancedxmlescape=true* | false");
|
||||
System.err.println(" transrescharstoncr=true | false*");
|
||||
System.err.println(" usecdata=true* | false");
|
||||
System.err.println(" specialentities=true* | false");
|
||||
System.err.println(" transspecialentitiestoncr=true | false*");
|
||||
System.err.println(" unicodechars=true* | false");
|
||||
System.err.println(" omitunknowntags=true | false*");
|
||||
System.err.println(" treatunknowntagsascontent=true | false*");
|
||||
System.err.println(" omitdeprtags=true | false*");
|
||||
System.err.println(" treatdeprtagsascontent=true | false*");
|
||||
System.err.println(" omitcomments=true | false*");
|
||||
System.err.println(" omitxmldecl=true | false*");
|
||||
System.err.println(" omitdoctypedecl=true* | false");
|
||||
System.err.println(" useemptyelementtags=true* | false");
|
||||
System.err.println(" allowmultiwordattributes=true* | false");
|
||||
System.err.println(" allowhtmlinsideattributes=true | false*");
|
||||
System.err.println(" ignoreqe=true* | false");
|
||||
System.err.println(" namespacesaware=true* | false");
|
||||
System.err.println(" hyphenreplacement=<string value> [=]");
|
||||
System.err.println(" prunetags=<string value> []");
|
||||
System.err.println(" booleanatts=self* | empty | true");
|
||||
System.err.println(" nodebyxpath=<xpath expression>");
|
||||
System.err.println(" omitenvelope=true | false*");
|
||||
System.err.println(" t:<sourcetagX>[=<desttag>[,<preserveatts>]]");
|
||||
System.err.println(" t:<sourcetagX>.<destattrY>[=<template>]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String inCharset = getArgValue(args, "incharset");
|
||||
if ("".equals(inCharset)) {
|
||||
inCharset = HtmlCleaner.DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
String outCharset = getArgValue(args, "outcharset");
|
||||
if ("".equals(outCharset)) {
|
||||
outCharset = HtmlCleaner.DEFAULT_CHARSET;
|
||||
}
|
||||
|
||||
String destination = getArgValue(args, "dest");
|
||||
String outputType = getArgValue(args, "outputtype");
|
||||
String advancedXmlEscape = getArgValue(args, "advancedxmlescape");
|
||||
String transResCharsToNCR = getArgValue(args, "transrescharstoncr");
|
||||
String useCData = getArgValue(args, "usecdata");
|
||||
String translateSpecialEntities = getArgValue(args, "specialentities");
|
||||
String transSpecialEntitiesToNCR = getArgValue(args, "transspecialentitiestoncr");
|
||||
String unicodeChars = getArgValue(args, "unicodechars");
|
||||
String omitUnknownTags = getArgValue(args, "omitunknowntags");
|
||||
String treatUnknownTagsAsContent = getArgValue(args, "treatunknowntagsascontent");
|
||||
String omitDeprecatedTags = getArgValue(args, "omitdeprtags");
|
||||
String treatDeprecatedTagsAsContent = getArgValue(args, "treatdeprtagsascontent");
|
||||
String omitComments = getArgValue(args, "omitcomments");
|
||||
String omitXmlDeclaration = getArgValue(args, "omitxmldecl");
|
||||
String omitDoctypeDeclaration = getArgValue(args, "omitdoctypedecl");
|
||||
String omitHtmlEnvelope = getArgValue(args, "omithtmlenvelope");
|
||||
String useEmptyElementTags = getArgValue(args, "useemptyelementtags");
|
||||
String allowMultiWordAttributes = getArgValue(args, "allowmultiwordattributes");
|
||||
String allowHtmlInsideAttributes = getArgValue(args, "allowhtmlinsideattributes");
|
||||
String ignoreQuestAndExclam = getArgValue(args, "ignoreqe");
|
||||
String namespacesAware= getArgValue(args, "namespacesaware");
|
||||
String commentHyphen = getArgValue(args, "hyphenreplacement");
|
||||
String pruneTags = getArgValue(args, "prunetags");
|
||||
String booleanAtts = getArgValue(args, "booleanatts");
|
||||
String nodeByXPath = getArgValue(args, "nodebyxpath");
|
||||
|
||||
boolean omitEnvelope = toBoolean( getArgValue(args, "omitenvelope") );
|
||||
|
||||
HtmlCleaner cleaner;
|
||||
|
||||
String tagInfoFile = getArgValue(args, "taginfofile");
|
||||
if ( !"".equals(tagInfoFile) ) {
|
||||
cleaner = new HtmlCleaner(new ConfigFileTagProvider(new File(tagInfoFile)));
|
||||
} else {
|
||||
cleaner = new HtmlCleaner();
|
||||
}
|
||||
|
||||
final CleanerProperties props = cleaner.getProperties();
|
||||
|
||||
if ( !"".equals(omitUnknownTags) ) {
|
||||
props.setOmitUnknownTags( toBoolean(omitUnknownTags) );
|
||||
}
|
||||
|
||||
if ( !"".equals(treatUnknownTagsAsContent) ) {
|
||||
props.setTreatUnknownTagsAsContent( toBoolean(treatUnknownTagsAsContent) );
|
||||
}
|
||||
|
||||
if ( !"".equals(omitDeprecatedTags) ) {
|
||||
props.setOmitDeprecatedTags( toBoolean(omitDeprecatedTags) );
|
||||
}
|
||||
|
||||
if ( !"".equals(treatDeprecatedTagsAsContent) ) {
|
||||
props.setTreatDeprecatedTagsAsContent( toBoolean(treatDeprecatedTagsAsContent) );
|
||||
}
|
||||
|
||||
if ( !"".equals(advancedXmlEscape) ) {
|
||||
props.setAdvancedXmlEscape( toBoolean(advancedXmlEscape) );
|
||||
}
|
||||
|
||||
if ( !"".equals(transResCharsToNCR) ) {
|
||||
props.setTransResCharsToNCR( toBoolean(transResCharsToNCR) );
|
||||
}
|
||||
|
||||
if ( !"".equals(useCData) ) {
|
||||
props.setUseCdataForScriptAndStyle( toBoolean(useCData) );
|
||||
}
|
||||
|
||||
if ( !"".equals(translateSpecialEntities) ) {
|
||||
props.setTranslateSpecialEntities( toBoolean(translateSpecialEntities) );
|
||||
}
|
||||
|
||||
if ( !"".equals(transSpecialEntitiesToNCR) ) {
|
||||
props.setTransSpecialEntitiesToNCR( toBoolean(transSpecialEntitiesToNCR) );
|
||||
}
|
||||
|
||||
if ( !"".equals(unicodeChars) ) {
|
||||
props.setRecognizeUnicodeChars( toBoolean(unicodeChars) );
|
||||
}
|
||||
|
||||
if ( !"".equals(omitComments) ) {
|
||||
props.setOmitComments( toBoolean(omitComments) );
|
||||
}
|
||||
|
||||
if ( !"".equals(omitXmlDeclaration) ) {
|
||||
props.setOmitXmlDeclaration( toBoolean(omitXmlDeclaration) );
|
||||
}
|
||||
|
||||
if ( !"".equals(omitDoctypeDeclaration) ) {
|
||||
props.setOmitDoctypeDeclaration( toBoolean(omitDoctypeDeclaration) );
|
||||
}
|
||||
|
||||
if ( !"".equals(omitHtmlEnvelope) ) {
|
||||
props.setOmitHtmlEnvelope( toBoolean(omitHtmlEnvelope) );
|
||||
}
|
||||
|
||||
if ( !"".equals(useEmptyElementTags) ) {
|
||||
props.setUseEmptyElementTags( toBoolean(useEmptyElementTags) );
|
||||
}
|
||||
|
||||
if ( !"".equals(allowMultiWordAttributes) ) {
|
||||
props.setAllowMultiWordAttributes( toBoolean(allowMultiWordAttributes) );
|
||||
}
|
||||
|
||||
if ( !"".equals(allowHtmlInsideAttributes) ) {
|
||||
props.setAllowHtmlInsideAttributes( toBoolean(allowHtmlInsideAttributes) );
|
||||
}
|
||||
|
||||
if ( !"".equals(ignoreQuestAndExclam) ) {
|
||||
props.setIgnoreQuestAndExclam( toBoolean(ignoreQuestAndExclam) );
|
||||
}
|
||||
|
||||
if ( !"".equals(namespacesAware) ) {
|
||||
props.setNamespacesAware( toBoolean(namespacesAware) );
|
||||
}
|
||||
|
||||
if ( !"".equals(commentHyphen) ) {
|
||||
props.setHyphenReplacementInComment(commentHyphen);
|
||||
}
|
||||
|
||||
if ( !"".equals(pruneTags) ) {
|
||||
props.setPruneTags(pruneTags);
|
||||
}
|
||||
|
||||
if ( !"".equals(booleanAtts) ) {
|
||||
props.setBooleanAttributeValues(booleanAtts);
|
||||
}
|
||||
|
||||
// collect transformation info
|
||||
Map transInfos = new TreeMap();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if (arg.startsWith("t:") && arg.length() > 2) {
|
||||
arg = arg.substring(2);
|
||||
int index = arg.indexOf('=');
|
||||
String key = index <= 0 ? arg : arg.substring(0, index);
|
||||
String value = index <= 0 ? null : arg.substring(index + 1);
|
||||
transInfos.put(key, value);
|
||||
}
|
||||
}
|
||||
if (transInfos != null) {
|
||||
CleanerTransformations transformations = new CleanerTransformations();
|
||||
Iterator iterator = transInfos.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String tag = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
Utils.updateTagTransformations(transformations, tag, value);
|
||||
}
|
||||
cleaner.setTransformations(transformations);
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
TagNode node;
|
||||
|
||||
String srcLowerCase = source.toLowerCase();
|
||||
if ( srcLowerCase.startsWith("http://") || srcLowerCase.startsWith("https://") ) {
|
||||
node = cleaner.clean(new URL(source), inCharset);
|
||||
} else {
|
||||
node = cleaner.clean(new File(source), inCharset);
|
||||
}
|
||||
|
||||
// if user specifies XPath expresssion to choose node for serialization, then
|
||||
// try to evaluate XPath and look for first TagNode instance in the resulting array
|
||||
if ( !"".equals(nodeByXPath) ) {
|
||||
final Object[] xpathResult = node.evaluateXPath(nodeByXPath);
|
||||
int i;
|
||||
for (i = 0; i < xpathResult.length; i++) {
|
||||
if ( xpathResult[i] instanceof TagNode ) {
|
||||
node = (TagNode) xpathResult[i];
|
||||
System.out.println("Node successfully found by XPath.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == xpathResult.length) {
|
||||
System.out.println("Node not found by XPath expression - whole html tree is going to be serialized!");
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream out;
|
||||
if ( destination == null || "".equals(destination.trim()) ) {
|
||||
out = System.out;
|
||||
} else {
|
||||
out = new FileOutputStream(destination);
|
||||
}
|
||||
|
||||
if ( "compact".equals(outputType) ) {
|
||||
new CompactXmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
} else if ( "browser-compact".equals(outputType) ) {
|
||||
new BrowserCompactXmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
} else if ( "pretty".equals(outputType) ) {
|
||||
new PrettyXmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
} else if ( "htmlsimple".equals(outputType) ) {
|
||||
new SimpleHtmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
} else if ( "htmlcompact".equals(outputType) ) {
|
||||
new CompactHtmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
} else if ( "htmlpretty".equals(outputType) ) {
|
||||
new PrettyHtmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
} else {
|
||||
new SimpleXmlSerializer(props).writeToStream(node, out, outCharset, omitEnvelope);
|
||||
}
|
||||
|
||||
System.out.println("Finished successfully in " + (System.currentTimeMillis() - start)+ "ms." );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.htmlcleaner.BaseToken;
|
||||
|
||||
/**
|
||||
* <p>HTML comment token.</p>
|
||||
*/
|
||||
public class CommentNode implements BaseToken, HtmlNode {
|
||||
|
||||
private StringBuilder content;
|
||||
|
||||
public CommentNode(String content) {
|
||||
this.content = new StringBuilder(content);
|
||||
}
|
||||
|
||||
public String getCommentedContent() {
|
||||
return "<!--" + content + "-->";
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getCommentedContent();
|
||||
}
|
||||
|
||||
public void serialize(Serializer serializer, Writer writer) throws IOException {
|
||||
writer.write( getCommentedContent() );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Compact HTML serializer - creates resulting HTML by stripping whitespaces wherever possible.</p>
|
||||
*/
|
||||
public class CompactHtmlSerializer extends HtmlSerializer {
|
||||
|
||||
private int openPreTags = 0;
|
||||
|
||||
public CompactHtmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
boolean isPreTag = "pre".equalsIgnoreCase(tagNode.getName());
|
||||
if (isPreTag) {
|
||||
openPreTags++;
|
||||
}
|
||||
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
List tagChildren = tagNode.getChildren();
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
ListIterator childrenIt = tagChildren.listIterator();
|
||||
while ( childrenIt.hasNext() ) {
|
||||
Object item = childrenIt.next();
|
||||
if (item instanceof ContentNode) {
|
||||
String content = item.toString();
|
||||
if (openPreTags > 0) {
|
||||
writer.write(content);
|
||||
} else {
|
||||
boolean startsWithSpace = content.length() > 0 && Character.isWhitespace( content.charAt(0) );
|
||||
boolean endsWithSpace = content.length() > 1 && Character.isWhitespace( content.charAt(content.length() - 1) );
|
||||
content = dontEscape(tagNode) ? content.trim() : escapeText(content.trim());
|
||||
|
||||
if (startsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
|
||||
if (content.length() != 0) {
|
||||
writer.write(content);
|
||||
if (endsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenIt.hasNext()) {
|
||||
if ( !Utils.isWhitespaceString(childrenIt.next()) ) {
|
||||
writer.write("\n");
|
||||
}
|
||||
childrenIt.previous();
|
||||
}
|
||||
}
|
||||
} else if (item instanceof CommentNode) {
|
||||
String content = ((CommentNode) item).getCommentedContent().trim();
|
||||
writer.write(content);
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken)item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
if (isPreTag) {
|
||||
openPreTags--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Compact XML serializer - creates resulting XML by stripping whitespaces.</p>
|
||||
*/
|
||||
public class CompactXmlSerializer extends XmlSerializer {
|
||||
|
||||
public CompactXmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
List tagChildren = tagNode.getChildren();
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
ListIterator childrenIt = tagChildren.listIterator();
|
||||
while ( childrenIt.hasNext() ) {
|
||||
Object item = childrenIt.next();
|
||||
if (item instanceof ContentNode) {
|
||||
String content = item.toString().trim();
|
||||
writer.write( dontEscape(tagNode) ? content.replaceAll("]]>", "]]>") : escapeXml(content) );
|
||||
|
||||
if (childrenIt.hasNext()) {
|
||||
if ( !Utils.isWhitespaceString(childrenIt.next()) ) {
|
||||
writer.write("\n");
|
||||
}
|
||||
childrenIt.previous();
|
||||
}
|
||||
} else if (item instanceof CommentNode) {
|
||||
String content = ((CommentNode) item).getCommentedContent().trim();
|
||||
writer.write(content);
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken)item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Default tag provider - reads XML file in specified format and creates tag infos
|
||||
*/
|
||||
public class ConfigFileTagProvider extends HashMap implements ITagInfoProvider {
|
||||
|
||||
// obtaining instance of the SAX parser factory
|
||||
static SAXParserFactory parserFactory = SAXParserFactory.newInstance();
|
||||
static {
|
||||
parserFactory.setValidating(false);
|
||||
parserFactory.setNamespaceAware(false);
|
||||
}
|
||||
|
||||
// tells whether to generate code of the tag provider class based on XML configuration file
|
||||
// to the standard output
|
||||
private boolean generateCode = false;
|
||||
|
||||
private ConfigFileTagProvider() {
|
||||
}
|
||||
|
||||
public ConfigFileTagProvider(InputSource inputSource) {
|
||||
try {
|
||||
new ConfigParser(this).parse(inputSource);
|
||||
} catch (Exception e) {
|
||||
throw new HtmlCleanerException("Error parsing tag configuration file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigFileTagProvider(File file) {
|
||||
try {
|
||||
new ConfigParser(this).parse(new InputSource(new FileReader(file)));
|
||||
} catch (Exception e) {
|
||||
throw new HtmlCleanerException("Error parsing tag configuration file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public ConfigFileTagProvider(URL url) {
|
||||
try {
|
||||
Object content = url.getContent();
|
||||
if (content instanceof InputStream) {
|
||||
InputStreamReader reader = new InputStreamReader((InputStream)content);
|
||||
new ConfigParser(this).parse(new InputSource(reader));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new HtmlCleanerException("Error parsing tag configuration file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public TagInfo getTagInfo(String tagName) {
|
||||
return (TagInfo) get(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates code for tag provider class from specified configuration XML file.
|
||||
* In order to create custom tag info provider, make config file and call this main method
|
||||
* with the specified file. Output will be generated on the standard output. This way default
|
||||
* tag provider (class DefaultTagProvider) is generated from default.xml which which is packaged
|
||||
* in the source distribution.
|
||||
*
|
||||
* @param args
|
||||
* @throws IOException
|
||||
* @throws SAXException
|
||||
* @throws ParserConfigurationException
|
||||
*/
|
||||
public static void main(String[] args) throws IOException, SAXException, ParserConfigurationException {
|
||||
final ConfigFileTagProvider provider = new ConfigFileTagProvider();
|
||||
provider.generateCode = true;
|
||||
|
||||
File configFile = new File("default.xml");
|
||||
String packagePath = "org.htmlcleaner";
|
||||
String className = "DefaultTagProvider";
|
||||
|
||||
final ConfigParser parser = provider.new ConfigParser(provider);
|
||||
System.out.println("package " + packagePath + ";");
|
||||
System.out.println("import java.util.HashMap;");
|
||||
System.out.println("public class " + className + " extends HashMap implements ITagInfoProvider {");
|
||||
System.out.println("public " + className + "() {");
|
||||
System.out.println("TagInfo tagInfo;");
|
||||
parser.parse( new InputSource(new FileReader(configFile)) );
|
||||
System.out.println("}");
|
||||
System.out.println("}");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SAX parser for tag configuration files.
|
||||
*/
|
||||
private class ConfigParser extends DefaultHandler {
|
||||
private TagInfo tagInfo = null;
|
||||
private String dependencyName = null;
|
||||
private Map tagInfoMap;
|
||||
|
||||
ConfigParser(Map tagInfoMap) {
|
||||
this.tagInfoMap = tagInfoMap;
|
||||
}
|
||||
|
||||
public void parse(InputSource in) throws ParserConfigurationException, SAXException, IOException {
|
||||
SAXParser parser = parserFactory.newSAXParser();
|
||||
parser.parse(in, this);
|
||||
}
|
||||
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
if (tagInfo != null) {
|
||||
String value = new String(ch, start, length).trim();
|
||||
if ( "fatal-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineFatalTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineFatalTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "req-enclosing-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineRequiredEnclosingTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineRequiredEnclosingTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "forbidden-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineForbiddenTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineForbiddenTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "allowed-children-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineAllowedChildrenTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineAllowedChildrenTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "higher-level-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineHigherLevelTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineHigherLevelTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "close-before-copy-inside-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineCloseBeforeCopyInsideTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineCloseBeforeCopyInsideTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "close-inside-copy-after-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineCloseInsideCopyAfterTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineCloseInsideCopyAfterTags(\"" + value + "\");");
|
||||
}
|
||||
} else if ( "close-before-tags".equals(dependencyName) ) {
|
||||
tagInfo.defineCloseBeforeTags(value);
|
||||
if (generateCode) {
|
||||
System.out.println("tagInfo.defineCloseBeforeTags(\"" + value + "\");");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
||||
if ( "tag".equals(qName) ) {
|
||||
String name = attributes.getValue("name");
|
||||
String content = attributes.getValue("content");
|
||||
String section = attributes.getValue("section");
|
||||
String deprecated = attributes.getValue("deprecated");
|
||||
String unique = attributes.getValue("unique");
|
||||
String ignorePermitted = attributes.getValue("ignore-permitted");
|
||||
tagInfo = new TagInfo(name,
|
||||
"all".equals(content) ? TagInfo.CONTENT_ALL : ("none".equals(content) ? TagInfo.CONTENT_NONE : TagInfo.CONTENT_TEXT),
|
||||
"all".equals(section) ? TagInfo.HEAD_AND_BODY : ("head".equals(section) ? TagInfo.HEAD : TagInfo.BODY),
|
||||
deprecated != null && "true".equals(deprecated),
|
||||
unique != null && "true".equals(unique),
|
||||
ignorePermitted != null && "true".equals(ignorePermitted) );
|
||||
if (generateCode) {
|
||||
String s = "tagInfo = new TagInfo(\"#1\", #2, #3, #4, #5, #6);";
|
||||
s = s.replaceAll("#1", name);
|
||||
s = s.replaceAll("#2", "all".equals(content) ? "TagInfo.CONTENT_ALL" : ("none".equals(content) ? "TagInfo.CONTENT_NONE" : " TagInfo.CONTENT_TEXT"));
|
||||
s = s.replaceAll("#3", "all".equals(section) ? "TagInfo.HEAD_AND_BODY" : ("head".equals(section) ? "TagInfo.HEAD" : "TagInfo.BODY"));
|
||||
s = s.replaceAll("#4", Boolean.toString(deprecated != null && "true".equals(deprecated)));
|
||||
s = s.replaceAll("#5", Boolean.toString(unique != null && "true".equals(unique)));
|
||||
s = s.replaceAll("#6", Boolean.toString(ignorePermitted != null && "true".equals(ignorePermitted)));
|
||||
System.out.println(s);
|
||||
}
|
||||
} else if ( !"tags".equals(qName) ) {
|
||||
dependencyName = qName;
|
||||
}
|
||||
}
|
||||
|
||||
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||
if ( "tag".equals(qName) ) {
|
||||
if (tagInfo != null) {
|
||||
tagInfoMap.put(tagInfo.getName(), tagInfo);
|
||||
if (generateCode) {
|
||||
System.out.println("this.put(\"" + tagInfo.getName() + "\", tagInfo);\n");
|
||||
}
|
||||
}
|
||||
tagInfo = null;
|
||||
} else if ( !"tags".equals(qName) ) {
|
||||
dependencyName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>HTML text token.</p>
|
||||
*/
|
||||
public class ContentNode implements BaseToken, HtmlNode {
|
||||
|
||||
private StringBuilder content;
|
||||
|
||||
public ContentNode(String content) {
|
||||
this.content = new StringBuilder(content);
|
||||
}
|
||||
|
||||
ContentNode(char content[], int len) {
|
||||
this.content = new StringBuilder(len + 16);
|
||||
this.content.append(content, 0, len);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void serialize(Serializer serializer, Writer writer) throws IOException {
|
||||
writer.write( content.toString() );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class is automatically created from ConfigFileTagProvider which reads
|
||||
* default XML configuration file with tag descriptions.
|
||||
* It is used as default tag info provider.
|
||||
* Class is created for performance purposes - parsing XML file requires some
|
||||
* processing time.
|
||||
*/
|
||||
public class DefaultTagProvider extends HashMap<String, TagInfo> implements ITagInfoProvider {
|
||||
|
||||
// singleton instance, used if no other TagInfoProvider is specified
|
||||
private static DefaultTagProvider _instance;
|
||||
|
||||
/**
|
||||
* @return Singleton instance of this class.
|
||||
*/
|
||||
public static synchronized DefaultTagProvider getInstance() {
|
||||
if (_instance == null) {
|
||||
_instance = new DefaultTagProvider();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public DefaultTagProvider() {
|
||||
TagInfo tagInfo;
|
||||
|
||||
tagInfo = new TagInfo("div", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("div", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("span", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("span", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("meta", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("meta", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("link", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("link", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("title", TagInfo.CONTENT_TEXT, TagInfo.HEAD, false, true, false);
|
||||
this.put("title", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("style", TagInfo.CONTENT_TEXT, TagInfo.HEAD, false, false, false);
|
||||
this.put("style", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("bgsound", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("bgsound", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h1", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h1", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h2", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h2", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h3", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h3", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h4", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h4", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h5", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h5", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h6", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h6", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("p", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("p", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("strong", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("strong", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("em", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("em", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("abbr", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("abbr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("acronym", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("acronym", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("address", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("address", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("bdo", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("bdo", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("blockquote", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("blockquote", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("cite", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("cite", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("q", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("q", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("code", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("code", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("ins", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("ins", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("del", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("del", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dfn", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("dfn", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("kbd", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("kbd", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("pre", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("pre", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("samp", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("samp", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("listing", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("listing", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("var", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("var", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("br", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
this.put("br", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("wbr", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
this.put("wbr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("nobr", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("nobr");
|
||||
this.put("nobr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("xmp", TagInfo.CONTENT_TEXT, TagInfo.BODY, false, false, false);
|
||||
this.put("xmp", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("a", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("a");
|
||||
this.put("a", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("base", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("base", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("img", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
this.put("img", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("area", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("map");
|
||||
tagInfo.defineCloseBeforeTags("area");
|
||||
this.put("area", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("map", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("map");
|
||||
this.put("map", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("object", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("object", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("param", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("param", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("applet", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
this.put("applet", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("xml", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("xml", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("ul", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("ul", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("ol", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("ol", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("li", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("li,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("li", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dl", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("dl", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dt", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("dt,dd");
|
||||
this.put("dt", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dd", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("dt,dd");
|
||||
this.put("dd", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("menu", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("menu", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dir", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("dir", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("table", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineAllowedChildrenTags("tr,tbody,thead,tfoot,colgroup,col,form,caption,tr");
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("tr,thead,tbody,tfoot,caption,colgroup,table,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param");
|
||||
this.put("table", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tr", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineRequiredEnclosingTags("tbody");
|
||||
tagInfo.defineAllowedChildrenTags("td,th");
|
||||
tagInfo.defineHigherLevelTags("thead,tfoot");
|
||||
tagInfo.defineCloseBeforeTags("tr,td,th,caption,colgroup");
|
||||
this.put("tr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("td", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineRequiredEnclosingTags("tr");
|
||||
tagInfo.defineCloseBeforeTags("td,th,caption,colgroup");
|
||||
this.put("td", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("th", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineRequiredEnclosingTags("tr");
|
||||
tagInfo.defineCloseBeforeTags("td,th,caption,colgroup");
|
||||
this.put("th", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tbody", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("tr,form");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("tbody", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("thead", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("tr,form");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("thead", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tfoot", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("tr,form");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("tfoot", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("col", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
this.put("col", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("colgroup", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("col");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("colgroup", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("caption", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("caption", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("form", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineForbiddenTags("form");
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("option,optgroup,textarea,select,fieldset,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("form", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("input", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("select,optgroup,option");
|
||||
this.put("input", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("textarea", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("select,optgroup,option");
|
||||
this.put("textarea", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("select", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineAllowedChildrenTags("option,optgroup");
|
||||
tagInfo.defineCloseBeforeTags("option,optgroup,select");
|
||||
this.put("select", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("option", TagInfo.CONTENT_TEXT, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineFatalTags("select");
|
||||
tagInfo.defineCloseBeforeTags("option");
|
||||
this.put("option", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("optgroup", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineFatalTags("select");
|
||||
tagInfo.defineAllowedChildrenTags("option");
|
||||
tagInfo.defineCloseBeforeTags("optgroup");
|
||||
this.put("optgroup", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("button", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("select,optgroup,option");
|
||||
this.put("button", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("label", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("label", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("fieldset", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("fieldset", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("legend", TagInfo.CONTENT_TEXT, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineRequiredEnclosingTags("fieldset");
|
||||
tagInfo.defineCloseBeforeTags("legend");
|
||||
this.put("legend", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("isindex", TagInfo.CONTENT_NONE, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("isindex", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("script", TagInfo.CONTENT_ALL, TagInfo.HEAD_AND_BODY, false, false, false);
|
||||
this.put("script", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("noscript", TagInfo.CONTENT_ALL, TagInfo.HEAD_AND_BODY, false, false, false);
|
||||
this.put("noscript", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("b", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("u,i,tt,sub,sup,big,small,strike,blink,s");
|
||||
this.put("b", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("i", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,tt,sub,sup,big,small,strike,blink,s");
|
||||
this.put("i", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("u", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,i,tt,sub,sup,big,small,strike,blink,s");
|
||||
this.put("u", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tt", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,sub,sup,big,small,strike,blink,s");
|
||||
this.put("tt", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("sub", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sup,big,small,strike,blink,s");
|
||||
this.put("sub", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("sup", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,big,small,strike,blink,s");
|
||||
this.put("sup", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("big", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,small,strike,blink,s");
|
||||
this.put("big", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("small", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,strike,blink,s");
|
||||
this.put("small", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("strike", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,small,blink,s");
|
||||
this.put("strike", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("blink", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,small,strike,s");
|
||||
this.put("blink", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("marquee", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("marquee", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("s", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,small,strike,blink");
|
||||
this.put("s", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("hr", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("hr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("font", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
this.put("font", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("basefont", TagInfo.CONTENT_NONE, TagInfo.BODY, true, false, false);
|
||||
this.put("basefont", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("center", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("center", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("comment", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("comment", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("server", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("server", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("iframe", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("iframe", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("embed", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("embed", tagInfo);
|
||||
}
|
||||
|
||||
public TagInfo getTagInfo(String tagName) {
|
||||
return get(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes tag info with specified name.
|
||||
* @param tagName Name of the tag to be removed from the tag provider.
|
||||
*/
|
||||
public void removeTagInfo(String tagName) {
|
||||
if (tagName != null) {
|
||||
remove(tagName.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new tag info.
|
||||
* @param tagInfo tag info to be added to the provider.
|
||||
*/
|
||||
public void addTagInfo(TagInfo tagInfo) {
|
||||
if (tagInfo != null) {
|
||||
put(tagInfo.getName().toLowerCase(), tagInfo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.htmlcleaner.BaseToken;
|
||||
|
||||
/**
|
||||
* <p>HTML doctype token.</p>
|
||||
*/
|
||||
public class DoctypeToken implements BaseToken {
|
||||
|
||||
private String part1;
|
||||
private String part2;
|
||||
private String part3;
|
||||
private String part4;
|
||||
|
||||
public DoctypeToken(String part1, String part2, String part3, String part4) {
|
||||
this.part1 = part1 != null ? part1.toUpperCase() : part1;
|
||||
this.part2 = part2 != null ? part2.toUpperCase() : part2;
|
||||
this.part3 = clean(part3);
|
||||
this.part4 = clean(part4);
|
||||
}
|
||||
|
||||
private String clean(String s) {
|
||||
if (s != null) {
|
||||
s = s.replace('>', ' ');
|
||||
s = s.replace('<', ' ');
|
||||
s = s.replace('&', ' ');
|
||||
s = s.replace('\'', ' ');
|
||||
s = s.replace('\"', ' ');
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
if ( part1 == null || "".equals(part1) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !"public".equalsIgnoreCase(part2) && !"system".equalsIgnoreCase(part2) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( "system".equalsIgnoreCase(part2) && part4 != null && !"".equals(part4) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( "public".equalsIgnoreCase(part2) && (part4 == null || "".equals(part4)) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
String result = "<!DOCTYPE " + part1 + " ";
|
||||
result += part2 + " \"" + part3 + "\"";
|
||||
if ( part4 != null && !"".equals(part4) ) {
|
||||
result += " \"" + part4 + "\"";
|
||||
}
|
||||
|
||||
result += ">";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getContent();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void serialize(Serializer serializer, Writer writer) throws IOException {
|
||||
writer.write(getContent() + "\n");
|
||||
}
|
||||
|
||||
public String getPart1() {
|
||||
return part1;
|
||||
}
|
||||
|
||||
public String getPart2() {
|
||||
return part2;
|
||||
}
|
||||
|
||||
public String getPart3() {
|
||||
return part3;
|
||||
}
|
||||
|
||||
public String getPart4() {
|
||||
return part4;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package org.htmlcleaner;
|
||||
|
||||
import org.w3c.dom.Comment;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>DOM serializer - creates xml DOM.</p>
|
||||
*/
|
||||
public class DomSerializer {
|
||||
|
||||
protected CleanerProperties props;
|
||||
protected boolean escapeXml = true;
|
||||
|
||||
public DomSerializer(CleanerProperties props, boolean escapeXml) {
|
||||
this.props = props;
|
||||
this.escapeXml = escapeXml;
|
||||
}
|
||||
|
||||
public DomSerializer(CleanerProperties props) {
|
||||
this(props, true);
|
||||
}
|
||||
|
||||
public Document createDOM(TagNode rootNode) throws ParserConfigurationException {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
|
||||
Document document = factory.newDocumentBuilder().newDocument();
|
||||
Element rootElement = createElement(rootNode, document);
|
||||
document.appendChild(rootElement);
|
||||
|
||||
setAttributes(rootNode, rootElement);
|
||||
|
||||
createSubnodes(document, rootElement, rootNode.getChildren());
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private Element createElement(TagNode node, Document document) {
|
||||
String name = node.getName();
|
||||
boolean nsAware = props.isNamespacesAware();
|
||||
String prefix = Utils.getXmlNSPrefix(name);
|
||||
Map<String, String> nsDeclarations = node.getNamespaceDeclarations();
|
||||
String nsURI = null;
|
||||
if (prefix != null) {
|
||||
if (nsAware) {
|
||||
if (nsDeclarations != null) {
|
||||
nsURI = nsDeclarations.get(prefix);
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = node.getNamespaceURIOnPath(prefix);
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = prefix;
|
||||
}
|
||||
} else {
|
||||
name = Utils.getXmlName(name);
|
||||
}
|
||||
} else {
|
||||
if (nsAware) {
|
||||
if (nsDeclarations != null) {
|
||||
nsURI = nsDeclarations.get("");
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = node.getNamespaceURIOnPath(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nsAware && nsURI != null) {
|
||||
return document.createElementNS(nsURI, name);
|
||||
} else {
|
||||
return document.createElement(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAttributes(TagNode node, Element element) {
|
||||
for (Map.Entry<String, String> entry: node.getAttributes().entrySet()) {
|
||||
String attrName = entry.getKey();
|
||||
String attrValue = entry.getValue();
|
||||
if (escapeXml) {
|
||||
attrValue = Utils.escapeXml(attrValue, props, true);
|
||||
}
|
||||
|
||||
String attPrefix = Utils.getXmlNSPrefix(attrName);
|
||||
if (attPrefix != null) {
|
||||
if (props.isNamespacesAware()) {
|
||||
String nsURI = node.getNamespaceURIOnPath(attPrefix);
|
||||
if (nsURI == null) {
|
||||
nsURI = attPrefix;
|
||||
}
|
||||
element.setAttributeNS(nsURI, attrName, attrValue);
|
||||
} else {
|
||||
element.setAttribute(Utils.getXmlName(attrName), attrValue);
|
||||
}
|
||||
} else {
|
||||
element.setAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createSubnodes(Document document, Element element, List tagChildren) {
|
||||
if (tagChildren != null) {
|
||||
Iterator it = tagChildren.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object item = it.next();
|
||||
if (item instanceof CommentNode) {
|
||||
CommentNode commentNode = (CommentNode) item;
|
||||
Comment comment = document.createComment( commentNode.getContent().toString() );
|
||||
element.appendChild(comment);
|
||||
} else if (item instanceof ContentNode) {
|
||||
String nodeName = element.getNodeName();
|
||||
String content = item.toString();
|
||||
boolean specialCase = props.isUseCdataForScriptAndStyle() &&
|
||||
("script".equalsIgnoreCase(nodeName) || "style".equalsIgnoreCase(nodeName));
|
||||
if (escapeXml && !specialCase) {
|
||||
content = Utils.escapeXml(content, props, true);
|
||||
}
|
||||
element.appendChild( specialCase ? document.createCDATASection(content) : document.createTextNode(content) );
|
||||
} else if (item instanceof TagNode) {
|
||||
TagNode subTagNode = (TagNode) item;
|
||||
Element subelement = createElement(subTagNode, document);
|
||||
|
||||
setAttributes(subTagNode, subelement);
|
||||
|
||||
// recursively create subnodes
|
||||
createSubnodes(document, subelement, subTagNode.getChildren());
|
||||
|
||||
element.appendChild(subelement);
|
||||
} else if (item instanceof List) {
|
||||
List sublist = (List) item;
|
||||
createSubnodes(document, element, sublist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.Writer;
|
||||
|
||||
|
||||
/**
|
||||
* <p>HTML tag end token.</p>
|
||||
*/
|
||||
public class EndTagToken extends TagToken {
|
||||
|
||||
public EndTagToken() {
|
||||
}
|
||||
|
||||
public EndTagToken(String name) {
|
||||
super(name == null ? null : name.toLowerCase());
|
||||
}
|
||||
|
||||
void setAttribute(String attName, String attValue) {
|
||||
// do nothing - simply ignore attributes in closing tag
|
||||
}
|
||||
|
||||
public void serialize(Serializer serializer, Writer writer) {
|
||||
// do nothing - simply ignore serialization
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,884 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Main HtmlCleaner class.
|
||||
*
|
||||
* <p>It represents public interface to the user. It's task is to call tokenizer with
|
||||
* specified source HTML, traverse list of produced token list and create internal
|
||||
* object model. It also offers a set of methods to write resulting XML to string,
|
||||
* file or any output stream.</p>
|
||||
* <p>Typical usage is the following:</p>
|
||||
*
|
||||
* <xmp>
|
||||
* // create an instance of HtmlCleaner
|
||||
* HtmlCleaner cleaner = new HtmlCleaner();
|
||||
*
|
||||
* // take default cleaner properties
|
||||
* CleanerProperties props = cleaner.getProperties();
|
||||
*
|
||||
* // customize cleaner's behaviour with property setters
|
||||
* props.setXXX(...);
|
||||
*
|
||||
* // Clean HTML taken from simple string, file, URL, input stream,
|
||||
* // input source or reader. Result is root node of created
|
||||
* // tree-like structure. Single cleaner instance may be safely used
|
||||
* // multiple times.
|
||||
* TagNode node = cleaner.clean(...);
|
||||
*
|
||||
* // optionally find parts of the DOM or modify some nodes
|
||||
* TagNode[] myNodes = node.getElementsByXXX(...);
|
||||
* // and/or
|
||||
* Object[] myNodes = node.evaluateXPath(xPathExpression);
|
||||
* // and/or
|
||||
* aNode.removeFromTree();
|
||||
* // and/or
|
||||
* aNode.addAttribute(attName, attValue);
|
||||
* // and/or
|
||||
* aNode.removeAttribute(attName, attValue);
|
||||
* // and/or
|
||||
* cleaner.setInnerHtml(aNode, htmlContent);
|
||||
* // and/or do some other tree manipulation/traversal
|
||||
*
|
||||
* // serialize a node to a file, output stream, DOM, JDom...
|
||||
* new XXXSerializer(props).writeXmlXXX(aNode, ...);
|
||||
* myJDom = new JDomSerializer(props, true).createJDom(aNode);
|
||||
* myDom = new DomSerializer(props, true).createDOM(aNode);
|
||||
* </xmp>
|
||||
*/
|
||||
public class HtmlCleaner {
|
||||
|
||||
public static final String DEFAULT_CHARSET = System.getProperty("file.encoding");
|
||||
|
||||
/**
|
||||
* Contains information about single open tag
|
||||
*/
|
||||
private class TagPos {
|
||||
private int position;
|
||||
private String name;
|
||||
private TagInfo info;
|
||||
|
||||
TagPos(int position, String name) {
|
||||
this.position = position;
|
||||
this.name = name;
|
||||
this.info = tagInfoProvider.getTagInfo(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that contains information and mathods for managing list of open,
|
||||
* but unhandled tags.
|
||||
*/
|
||||
private class OpenTags {
|
||||
private List<TagPos> list = new ArrayList<TagPos>();
|
||||
private TagPos last = null;
|
||||
private Set<String> set = new HashSet<String>();
|
||||
|
||||
private boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
private void addTag(String tagName, int position) {
|
||||
last = new TagPos(position, tagName);
|
||||
list.add(last);
|
||||
set.add(tagName);
|
||||
}
|
||||
|
||||
private void removeTag(String tagName) {
|
||||
ListIterator<TagPos> it = list.listIterator( list.size() );
|
||||
while ( it.hasPrevious() ) {
|
||||
TagPos currTagPos = it.previous();
|
||||
if (tagName.equals(currTagPos.name)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
last = list.isEmpty() ? null : list.get( list.size() - 1 );
|
||||
}
|
||||
|
||||
private TagPos findFirstTagPos() {
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
private TagPos getLastTagPos() {
|
||||
return last;
|
||||
}
|
||||
|
||||
private TagPos findTag(String tagName) {
|
||||
if (tagName != null) {
|
||||
ListIterator<TagPos> it = list.listIterator(list.size());
|
||||
String fatalTag = null;
|
||||
TagInfo fatalInfo = tagInfoProvider.getTagInfo(tagName);
|
||||
if (fatalInfo != null) {
|
||||
fatalTag = fatalInfo.getFatalTag();
|
||||
}
|
||||
|
||||
while (it.hasPrevious()) {
|
||||
TagPos currTagPos = it.previous();
|
||||
if (tagName.equals(currTagPos.name)) {
|
||||
return currTagPos;
|
||||
} else if (fatalTag != null && fatalTag.equals(currTagPos.name)) {
|
||||
// do not search past a fatal tag for this tag
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean tagExists(String tagName) {
|
||||
TagPos tagPos = findTag(tagName);
|
||||
return tagPos != null;
|
||||
}
|
||||
|
||||
private TagPos findTagToPlaceRubbish() {
|
||||
TagPos result = null, prev = null;
|
||||
|
||||
if ( !isEmpty() ) {
|
||||
ListIterator<TagPos> it = list.listIterator( list.size() );
|
||||
while ( it.hasPrevious() ) {
|
||||
result = it.previous();
|
||||
if ( result.info == null || result.info.allowsAnything() ) {
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
prev = result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean tagEncountered(String tagName) {
|
||||
return set.contains(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of tags specified in the set are already open.
|
||||
* @param tags
|
||||
*/
|
||||
private boolean someAlreadyOpen(Set tags) {
|
||||
Iterator<TagPos> it = list.iterator();
|
||||
while ( it.hasNext() ) {
|
||||
TagPos curr = it.next();
|
||||
if ( tags.contains(curr.name) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class CleanTimeValues {
|
||||
private OpenTags _openTags;
|
||||
private boolean _headOpened = false;
|
||||
private boolean _bodyOpened = false;
|
||||
private Set _headTags = new LinkedHashSet();
|
||||
private Set allTags = new TreeSet();
|
||||
|
||||
private TagNode htmlNode;
|
||||
private TagNode bodyNode;
|
||||
private TagNode headNode;
|
||||
private TagNode rootNode;
|
||||
|
||||
private Set<String> pruneTagSet = new HashSet<String>();
|
||||
private Set<TagNode> pruneNodeSet = new HashSet<TagNode>();
|
||||
}
|
||||
|
||||
private CleanerProperties properties;
|
||||
|
||||
private ITagInfoProvider tagInfoProvider;
|
||||
|
||||
private CleanerTransformations transformations = null;
|
||||
|
||||
/**
|
||||
* Constructor - creates cleaner instance with default tag info provider and default properties.
|
||||
*/
|
||||
public HtmlCleaner() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - creates the instance with specified tag info provider and default properties
|
||||
* @param tagInfoProvider Provider for tag filtering and balancing
|
||||
*/
|
||||
public HtmlCleaner(ITagInfoProvider tagInfoProvider) {
|
||||
this(tagInfoProvider, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - creates the instance with default tag info provider and specified properties
|
||||
* @param properties Properties used during parsing and serializing
|
||||
*/
|
||||
public HtmlCleaner(CleanerProperties properties) {
|
||||
this(null, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - creates the instance with specified tag info provider and specified properties
|
||||
* @param tagInfoProvider Provider for tag filtering and balancing
|
||||
* @param properties Properties used during parsing and serializing
|
||||
*/
|
||||
public HtmlCleaner(ITagInfoProvider tagInfoProvider, CleanerProperties properties) {
|
||||
this.tagInfoProvider = tagInfoProvider == null ? DefaultTagProvider.getInstance() : tagInfoProvider;
|
||||
this.properties = properties == null ? new CleanerProperties() : properties;
|
||||
this.properties.tagInfoProvider = this.tagInfoProvider;
|
||||
}
|
||||
|
||||
public TagNode clean(String htmlContent) {
|
||||
try {
|
||||
return clean( new StringReader(htmlContent) );
|
||||
} catch (IOException e) {
|
||||
// should never happen because reading from StringReader
|
||||
throw new HtmlCleanerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public TagNode clean(File file, String charset) throws IOException {
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
Reader reader = new InputStreamReader(in, charset);
|
||||
return clean(reader);
|
||||
}
|
||||
|
||||
public TagNode clean(File file) throws IOException {
|
||||
return clean(file, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
public TagNode clean(URL url, String charset) throws IOException {
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
if (charset == null) {
|
||||
charset = Utils.getCharsetFromContentTypeString( urlConnection.getHeaderField("Content-Type") );
|
||||
}
|
||||
if (charset == null) {
|
||||
charset = Utils.getCharsetFromContent(url);
|
||||
}
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
return clean(url.openStream(), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instance from the content downloaded from specified URL.
|
||||
* HTML encoding is resolved following the attempts in the sequence:
|
||||
* 1. reading Content-Type response header, 2. Analyzing META tags at the
|
||||
* beginning of the html, 3. Using platform's default charset.
|
||||
* @param url
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public TagNode clean(URL url) throws IOException {
|
||||
return clean(url, null);
|
||||
}
|
||||
|
||||
public TagNode clean(InputStream in, String charset) throws IOException {
|
||||
return clean( new InputStreamReader(in, charset) );
|
||||
}
|
||||
|
||||
public TagNode clean(InputStream in) throws IOException {
|
||||
return clean(in, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
public TagNode clean(Reader reader) throws IOException {
|
||||
return clean(reader, new CleanTimeValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic version of the cleaning call.
|
||||
* @param reader
|
||||
* @return An instance of TagNode object which is the root of the XML tree.
|
||||
* @throws IOException
|
||||
*/
|
||||
public TagNode clean(Reader reader, final CleanTimeValues cleanTimeValues) throws IOException {
|
||||
cleanTimeValues._openTags = new OpenTags();
|
||||
cleanTimeValues._headOpened = false;
|
||||
cleanTimeValues._bodyOpened = false;
|
||||
cleanTimeValues._headTags.clear();
|
||||
cleanTimeValues.allTags.clear();
|
||||
setPruneTags(properties.pruneTags, cleanTimeValues);
|
||||
|
||||
cleanTimeValues.htmlNode = createTagNode("html", cleanTimeValues);
|
||||
cleanTimeValues.bodyNode = createTagNode("body", cleanTimeValues);
|
||||
cleanTimeValues.headNode = createTagNode("head", cleanTimeValues);
|
||||
cleanTimeValues.rootNode = null;
|
||||
cleanTimeValues.htmlNode.addChild(cleanTimeValues.headNode);
|
||||
cleanTimeValues.htmlNode.addChild(cleanTimeValues.bodyNode);
|
||||
|
||||
HtmlTokenizer htmlTokenizer = new HtmlTokenizer(reader, properties, transformations, tagInfoProvider) {
|
||||
@Override
|
||||
void makeTree(List<BaseToken> tokenList) {
|
||||
HtmlCleaner.this.makeTree( tokenList, tokenList.listIterator(tokenList.size() - 1), cleanTimeValues );
|
||||
}
|
||||
|
||||
@Override
|
||||
TagNode createTagNode(String name) {
|
||||
return HtmlCleaner.this.createTagNode(name, cleanTimeValues);
|
||||
}
|
||||
};
|
||||
|
||||
htmlTokenizer.start();
|
||||
|
||||
List<BaseToken> nodeList = htmlTokenizer.getTokenList();
|
||||
closeAll(nodeList, cleanTimeValues);
|
||||
createDocumentNodes(nodeList, cleanTimeValues);
|
||||
|
||||
calculateRootNode(cleanTimeValues);
|
||||
|
||||
// if there are some nodes to prune from tree
|
||||
if ( cleanTimeValues.pruneNodeSet != null && !cleanTimeValues.pruneNodeSet.isEmpty() ) {
|
||||
Iterator iterator = cleanTimeValues.pruneNodeSet.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
TagNode tagNode = (TagNode) iterator.next();
|
||||
TagNode parent = tagNode.getParent();
|
||||
if (parent != null) {
|
||||
parent.removeChild(tagNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanTimeValues.rootNode.setDocType( htmlTokenizer.getDocType() );
|
||||
|
||||
return cleanTimeValues.rootNode;
|
||||
}
|
||||
|
||||
private TagNode createTagNode(String name, CleanTimeValues cleanTimeValues) {
|
||||
TagNode node = new TagNode(name);
|
||||
if ( cleanTimeValues.pruneTagSet != null && name != null && cleanTimeValues.pruneTagSet.contains(name.toLowerCase()) ) {
|
||||
cleanTimeValues.pruneNodeSet.add(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private TagNode makeTagNodeCopy(TagNode tagNode, CleanTimeValues cleanTimeValues) {
|
||||
TagNode copy = tagNode.makeCopy();
|
||||
if ( cleanTimeValues.pruneTagSet != null && cleanTimeValues.pruneTagSet.contains(tagNode.getName()) ) {
|
||||
cleanTimeValues.pruneNodeSet.add(copy);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns root node to internal variable.
|
||||
* Root node of the result depends on parameter "omitHtmlEnvelope".
|
||||
* If it is set, then first child of the body will be root node,
|
||||
* or html will be root node otherwise.
|
||||
*/
|
||||
private void calculateRootNode(CleanTimeValues cleanTimeValues) {
|
||||
cleanTimeValues.rootNode = cleanTimeValues.htmlNode;
|
||||
|
||||
if (properties.omitHtmlEnvelope) {
|
||||
List bodyChildren = cleanTimeValues.bodyNode.getChildren();
|
||||
if (bodyChildren != null) {
|
||||
for (Object child: bodyChildren) {
|
||||
// if found child that is tag itself, then return it
|
||||
if (child instanceof TagNode) {
|
||||
cleanTimeValues.rootNode = (TagNode)child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add attributes from specified map to the specified tag.
|
||||
* If some attribute already exist it is preserved.
|
||||
* @param tag
|
||||
* @param attributes
|
||||
*/
|
||||
private void addAttributesToTag(TagNode tag, Map attributes) {
|
||||
if (attributes != null) {
|
||||
Map tagAttributes = tag.getAttributes();
|
||||
Iterator it = attributes.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry currEntry = (Map.Entry) it.next();
|
||||
String attName = (String) currEntry.getKey();
|
||||
if ( !tagAttributes.containsKey(attName) ) {
|
||||
String attValue = (String) currEntry.getValue();
|
||||
tag.setAttribute(attName, attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if open fatal tag is missing if there is a fatal tag for
|
||||
* the specified tag.
|
||||
* @param tag
|
||||
*/
|
||||
private boolean isFatalTagSatisfied(TagInfo tag, CleanTimeValues cleanTimeValues) {
|
||||
if (tag != null) {
|
||||
String fatalTagName = tag.getFatalTag();
|
||||
return fatalTagName == null ? true : cleanTimeValues._openTags.tagExists(fatalTagName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if specified tag requires parent tag, but that parent
|
||||
* tag is missing in the appropriate context.
|
||||
* @param tag
|
||||
*/
|
||||
private boolean mustAddRequiredParent(TagInfo tag, CleanTimeValues cleanTimeValues) {
|
||||
if (tag != null) {
|
||||
String requiredParent = tag.getRequiredParent();
|
||||
if (requiredParent != null) {
|
||||
String fatalTag = tag.getFatalTag();
|
||||
int fatalTagPositon = -1;
|
||||
if (fatalTag != null) {
|
||||
TagPos tagPos = cleanTimeValues._openTags.findTag(fatalTag);
|
||||
if (tagPos != null) {
|
||||
fatalTagPositon = tagPos.position;
|
||||
}
|
||||
}
|
||||
|
||||
// iterates through the list of open tags from the end and check if there is some higher
|
||||
ListIterator<TagPos> it = cleanTimeValues._openTags.list.listIterator( cleanTimeValues._openTags.list.size() );
|
||||
while ( it.hasPrevious() ) {
|
||||
TagPos currTagPos = it.previous();
|
||||
if (tag.isHigher(currTagPos.name)) {
|
||||
return currTagPos.position <= fatalTagPositon;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private TagNode createTagNode(TagNode startTagToken) {
|
||||
startTagToken.setFormed();
|
||||
return startTagToken;
|
||||
}
|
||||
|
||||
private boolean isAllowedInLastOpenTag(BaseToken token, CleanTimeValues cleanTimeValues) {
|
||||
TagPos last = cleanTimeValues._openTags.getLastTagPos();
|
||||
if (last != null) {
|
||||
if (last.info != null) {
|
||||
return last.info.allowsItem(token);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveToLastOpenTag(List nodeList, BaseToken tokenToAdd, CleanTimeValues cleanTimeValues) {
|
||||
TagPos last = cleanTimeValues._openTags.getLastTagPos();
|
||||
if ( last != null && last.info != null && last.info.isIgnorePermitted() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
TagPos rubbishPos = cleanTimeValues._openTags.findTagToPlaceRubbish();
|
||||
if (rubbishPos != null) {
|
||||
TagNode startTagToken = (TagNode) nodeList.get(rubbishPos.position);
|
||||
startTagToken.addItemForMoving(tokenToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStartToken(Object o) {
|
||||
return (o instanceof TagNode) && !((TagNode)o).isFormed();
|
||||
}
|
||||
|
||||
void makeTree(List<BaseToken> nodeList, ListIterator<BaseToken> nodeIterator, CleanTimeValues cleanTimeValues) {
|
||||
// process while not reach the end of the list
|
||||
while ( nodeIterator.hasNext() ) {
|
||||
BaseToken token = nodeIterator.next();
|
||||
|
||||
if (token instanceof EndTagToken) {
|
||||
EndTagToken endTagToken = (EndTagToken) token;
|
||||
String tagName = endTagToken.getName();
|
||||
TagInfo tag = tagInfoProvider.getTagInfo(tagName);
|
||||
|
||||
if ( (tag == null && properties.omitUnknownTags) || (tag != null && tag.isDeprecated() && properties.omitDeprecatedTags) ) {
|
||||
nodeIterator.set(null);
|
||||
} else if ( tag != null && !tag.allowsBody() ) {
|
||||
nodeIterator.set(null);
|
||||
} else {
|
||||
TagPos matchingPosition = cleanTimeValues._openTags.findTag(tagName);
|
||||
|
||||
if (matchingPosition != null) {
|
||||
List closed = closeSnippet(nodeList, matchingPosition, endTagToken, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
for (int i = closed.size() - 1; i >= 1; i--) {
|
||||
TagNode closedTag = (TagNode) closed.get(i);
|
||||
if ( tag != null && tag.isContinueAfter(closedTag.getName()) ) {
|
||||
nodeIterator.add( makeTagNodeCopy(closedTag, cleanTimeValues) );
|
||||
nodeIterator.previous();
|
||||
}
|
||||
}
|
||||
} else if ( !isAllowedInLastOpenTag(token, cleanTimeValues) ) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
}
|
||||
}
|
||||
} else if ( isStartToken(token) ) {
|
||||
TagNode startTagToken = (TagNode) token;
|
||||
String tagName = startTagToken.getName();
|
||||
TagInfo tag = tagInfoProvider.getTagInfo(tagName);
|
||||
|
||||
TagPos lastTagPos = cleanTimeValues._openTags.isEmpty() ? null : cleanTimeValues._openTags.getLastTagPos();
|
||||
TagInfo lastTagInfo = lastTagPos == null ? null : tagInfoProvider.getTagInfo(lastTagPos.name);
|
||||
|
||||
// add tag to set of all tags
|
||||
cleanTimeValues.allTags.add(tagName);
|
||||
|
||||
// HTML open tag
|
||||
if ( "html".equals(tagName) ) {
|
||||
addAttributesToTag(cleanTimeValues.htmlNode, startTagToken.getAttributes());
|
||||
nodeIterator.set(null);
|
||||
// BODY open tag
|
||||
} else if ( "body".equals(tagName) ) {
|
||||
cleanTimeValues._bodyOpened = true;
|
||||
addAttributesToTag(cleanTimeValues.bodyNode, startTagToken.getAttributes());
|
||||
nodeIterator.set(null);
|
||||
// HEAD open tag
|
||||
} else if ( "head".equals(tagName) ) {
|
||||
cleanTimeValues._headOpened = true;
|
||||
addAttributesToTag(cleanTimeValues.headNode, startTagToken.getAttributes());
|
||||
nodeIterator.set(null);
|
||||
// unknown HTML tag and unknown tags are not allowed
|
||||
} else if ( (tag == null && properties.omitUnknownTags) || (tag != null && tag.isDeprecated() && properties.omitDeprecatedTags) ) {
|
||||
nodeIterator.set(null);
|
||||
// if current tag is unknown, unknown tags are allowed and last open tag doesn't allow any other tags in its body
|
||||
} else if ( tag == null && lastTagInfo != null && !lastTagInfo.allowsAnything() ) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
} else if ( tag != null && tag.hasPermittedTags() && cleanTimeValues._openTags.someAlreadyOpen(tag.getPermittedTags()) ) {
|
||||
nodeIterator.set(null);
|
||||
// if tag that must be unique, ignore this occurence
|
||||
} else if ( tag != null && tag.isUnique() && cleanTimeValues._openTags.tagEncountered(tagName) ) {
|
||||
nodeIterator.set(null);
|
||||
// if there is no required outer tag without that this open tag is ignored
|
||||
} else if ( !isFatalTagSatisfied(tag, cleanTimeValues) ) {
|
||||
nodeIterator.set(null);
|
||||
// if there is no required parent tag - it must be added before this open tag
|
||||
} else if ( mustAddRequiredParent(tag, cleanTimeValues) ) {
|
||||
String requiredParent = tag.getRequiredParent();
|
||||
TagNode requiredParentStartToken = createTagNode(requiredParent, cleanTimeValues);
|
||||
nodeIterator.previous();
|
||||
nodeIterator.add(requiredParentStartToken);
|
||||
nodeIterator.previous();
|
||||
// if last open tag has lower presidence then this, it must be closed
|
||||
} else if ( tag != null && lastTagPos != null && tag.isMustCloseTag(lastTagInfo) ) {
|
||||
List closed = closeSnippet(nodeList, lastTagPos, startTagToken, cleanTimeValues);
|
||||
int closedCount = closed.size();
|
||||
|
||||
// it is needed to copy some tags again in front of current, if there are any
|
||||
if ( tag.hasCopyTags() && closedCount > 0 ) {
|
||||
// first iterates over list from the back and collects all start tokens
|
||||
// in sequence that must be copied
|
||||
ListIterator closedIt = closed.listIterator(closedCount);
|
||||
List toBeCopied = new ArrayList();
|
||||
while (closedIt.hasPrevious()) {
|
||||
TagNode currStartToken = (TagNode) closedIt.previous();
|
||||
if ( tag.isCopy(currStartToken.getName()) ) {
|
||||
toBeCopied.add(0, currStartToken);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeCopied.size() > 0) {
|
||||
Iterator copyIt = toBeCopied.iterator();
|
||||
while (copyIt.hasNext()) {
|
||||
TagNode currStartToken = (TagNode) copyIt.next();
|
||||
nodeIterator.add( makeTagNodeCopy(currStartToken, cleanTimeValues) );
|
||||
}
|
||||
|
||||
// back to the previous place, before adding new start tokens
|
||||
for (int i = 0; i < toBeCopied.size(); i++) {
|
||||
nodeIterator.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeIterator.previous();
|
||||
// if this open tag is not allowed inside last open tag, then it must be moved to the place where it can be
|
||||
} else if ( !isAllowedInLastOpenTag(token, cleanTimeValues) ) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
// if it is known HTML tag but doesn't allow body, it is immediately closed
|
||||
} else if ( tag != null && !tag.allowsBody() ) {
|
||||
TagNode newTagNode = createTagNode(startTagToken);
|
||||
addPossibleHeadCandidate(tag, newTagNode, cleanTimeValues);
|
||||
nodeIterator.set(newTagNode);
|
||||
// default case - just remember this open tag and go further
|
||||
} else {
|
||||
cleanTimeValues._openTags.addTag( tagName, nodeIterator.previousIndex() );
|
||||
}
|
||||
} else {
|
||||
if ( !isAllowedInLastOpenTag(token, cleanTimeValues) ) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createDocumentNodes(List listNodes, CleanTimeValues cleanTimeValues) {
|
||||
Iterator it = listNodes.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object child = it.next();
|
||||
|
||||
if (child == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean toAdd = true;
|
||||
|
||||
if (child instanceof TagNode) {
|
||||
TagNode node = (TagNode) child;
|
||||
TagInfo tag = tagInfoProvider.getTagInfo( node.getName() );
|
||||
addPossibleHeadCandidate(tag, node, cleanTimeValues);
|
||||
} else {
|
||||
if (child instanceof ContentNode) {
|
||||
toAdd = !"".equals(child.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (toAdd) {
|
||||
cleanTimeValues.bodyNode.addChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
// move all viable head candidates to head section of the tree
|
||||
Iterator headIterator = cleanTimeValues._headTags.iterator();
|
||||
while (headIterator.hasNext()) {
|
||||
TagNode headCandidateNode = (TagNode) headIterator.next();
|
||||
|
||||
// check if this node is already inside a candidate for moving to head
|
||||
TagNode parent = headCandidateNode.getParent();
|
||||
boolean toMove = true;
|
||||
while (parent != null) {
|
||||
if ( cleanTimeValues._headTags.contains(parent) ) {
|
||||
toMove = false;
|
||||
break;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
if (toMove) {
|
||||
headCandidateNode.removeFromTree();
|
||||
cleanTimeValues.headNode.addChild(headCandidateNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List closeSnippet(List nodeList, TagPos tagPos, Object toNode, CleanTimeValues cleanTimeValues) {
|
||||
List closed = new ArrayList();
|
||||
ListIterator it = nodeList.listIterator(tagPos.position);
|
||||
|
||||
TagNode tagNode = null;
|
||||
Object item = it.next();
|
||||
boolean isListEnd = false;
|
||||
|
||||
while ( (toNode == null && !isListEnd) || (toNode != null && item != toNode) ) {
|
||||
if ( isStartToken(item) ) {
|
||||
TagNode startTagToken = (TagNode) item;
|
||||
closed.add(startTagToken);
|
||||
List<BaseToken> itemsToMove = startTagToken.getItemsToMove();
|
||||
if (itemsToMove != null) {
|
||||
OpenTags prevOpenTags = cleanTimeValues._openTags;
|
||||
cleanTimeValues._openTags = new OpenTags();
|
||||
makeTree(itemsToMove, itemsToMove.listIterator(0), cleanTimeValues);
|
||||
closeAll(itemsToMove, cleanTimeValues);
|
||||
startTagToken.setItemsToMove(null);
|
||||
cleanTimeValues._openTags = prevOpenTags;
|
||||
}
|
||||
|
||||
TagNode newTagNode = createTagNode(startTagToken);
|
||||
TagInfo tag = tagInfoProvider.getTagInfo( newTagNode.getName() );
|
||||
addPossibleHeadCandidate(tag, newTagNode, cleanTimeValues);
|
||||
if (tagNode != null) {
|
||||
tagNode.addChildren(itemsToMove);
|
||||
tagNode.addChild(newTagNode);
|
||||
it.set(null);
|
||||
} else {
|
||||
if (itemsToMove != null) {
|
||||
itemsToMove.add(newTagNode);
|
||||
it.set(itemsToMove);
|
||||
} else {
|
||||
it.set(newTagNode);
|
||||
}
|
||||
}
|
||||
|
||||
cleanTimeValues._openTags.removeTag( newTagNode.getName() );
|
||||
tagNode = newTagNode;
|
||||
} else {
|
||||
if (tagNode != null) {
|
||||
it.set(null);
|
||||
if (item != null) {
|
||||
tagNode.addChild(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( it.hasNext() ) {
|
||||
item = it.next();
|
||||
} else {
|
||||
isListEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
return closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all unclosed tags if there are any.
|
||||
*/
|
||||
private void closeAll(List<BaseToken> nodeList, CleanTimeValues cleanTimeValues) {
|
||||
TagPos firstTagPos = cleanTimeValues._openTags.findFirstTagPos();
|
||||
if (firstTagPos != null) {
|
||||
closeSnippet(nodeList, firstTagPos, null, cleanTimeValues);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified tag with specified info is candidate for moving to head section.
|
||||
* @param tagInfo
|
||||
* @param tagNode
|
||||
*/
|
||||
private void addPossibleHeadCandidate(TagInfo tagInfo, TagNode tagNode, CleanTimeValues cleanTimeValues) {
|
||||
if (tagInfo != null && tagNode != null) {
|
||||
if ( tagInfo.isHeadTag() || (tagInfo.isHeadAndBodyTag() && cleanTimeValues._headOpened && !cleanTimeValues._bodyOpened) ) {
|
||||
cleanTimeValues._headTags.add(tagNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CleanerProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void setPruneTags(String pruneTags, CleanTimeValues cleanTimeValues) {
|
||||
cleanTimeValues.pruneTagSet.clear();
|
||||
cleanTimeValues.pruneNodeSet.clear();
|
||||
if (pruneTags != null) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(pruneTags, ",");
|
||||
while ( tokenizer.hasMoreTokens() ) {
|
||||
cleanTimeValues.pruneTagSet.add( tokenizer.nextToken().trim().toLowerCase() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ITagInfoProvider instance for this HtmlCleaner
|
||||
*/
|
||||
public ITagInfoProvider getTagInfoProvider() {
|
||||
return tagInfoProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Transormations defined for this instance of cleaner
|
||||
*/
|
||||
public CleanerTransformations getTransformations() {
|
||||
return transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tranformations for this cleaner instance.
|
||||
* @param transformations
|
||||
*/
|
||||
public void setTransformations(CleanerTransformations transformations) {
|
||||
this.transformations = transformations;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the specified node, returns it's content as string.
|
||||
* @param node
|
||||
*/
|
||||
public String getInnerHtml(TagNode node) {
|
||||
if (node != null) {
|
||||
try {
|
||||
String content = new SimpleXmlSerializer(properties).getAsString(node);
|
||||
int index1 = content.indexOf("<" + node.getName());
|
||||
index1 = content.indexOf('>', index1 + 1);
|
||||
int index2 = content.lastIndexOf('<');
|
||||
return index1 >= 0 && index1 <= index2 ? content.substring(index1 + 1, index2) : null;
|
||||
} catch (IOException e) {
|
||||
throw new HtmlCleanerException(e);
|
||||
}
|
||||
} else {
|
||||
throw new HtmlCleanerException("Cannot return inner html of the null node!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the specified tag node, defines it's html content. This causes cleaner to
|
||||
* reclean given html portion and insert it inside the node instead of previous content.
|
||||
* @param node
|
||||
* @param content
|
||||
*/
|
||||
public void setInnerHtml(TagNode node, String content) {
|
||||
if (node != null) {
|
||||
String nodeName = node.getName();
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append("<" + nodeName + " marker=''>");
|
||||
html.append(content);
|
||||
html.append("</" + nodeName + ">");
|
||||
TagNode parent = node.getParent();
|
||||
while (parent != null) {
|
||||
String parentName = parent.getName();
|
||||
html.insert(0, "<" + parentName + ">");
|
||||
html.append("</" + parentName + ">");
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
TagNode rootNode = clean( html.toString() );
|
||||
TagNode cleanedNode = rootNode.findElementHavingAttribute("marker", true);
|
||||
if (cleanedNode != null) {
|
||||
node.setChildren( cleanedNode.getChildren() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
|
||||
/**
|
||||
* <p>General HtmlCleaner runtime exception.</p>
|
||||
*/
|
||||
public class HtmlCleanerException extends RuntimeException {
|
||||
|
||||
public HtmlCleanerException() {
|
||||
this("HtmlCleaner expression occureed!");
|
||||
}
|
||||
|
||||
public HtmlCleanerException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public HtmlCleanerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HtmlCleanerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
|
||||
import java.net.URL;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Support for ANT.</p>
|
||||
*/
|
||||
public class HtmlCleanerForAnt extends org.apache.tools.ant.Task {
|
||||
|
||||
private String text;
|
||||
private String src;
|
||||
private String dest;
|
||||
private String incharset = HtmlCleaner.DEFAULT_CHARSET;
|
||||
private String outcharset = HtmlCleaner.DEFAULT_CHARSET;
|
||||
private String taginfofile = null;
|
||||
private String outputtype = "simple";
|
||||
private boolean advancedxmlescape = true;
|
||||
private boolean transrescharstoncr = false;
|
||||
private boolean usecdata = true;
|
||||
private boolean specialentities = true;
|
||||
private boolean transspecialentitiestoncr = false;
|
||||
private boolean unicodechars = true;
|
||||
private boolean omitunknowntags = false;
|
||||
private boolean treatunknowntagsascontent = false;
|
||||
private boolean omitdeprtags = false;
|
||||
private boolean treatdeprtagsascontent = false;
|
||||
private boolean omitcomments = false;
|
||||
private boolean omitxmldecl = false;
|
||||
private boolean omitdoctypedecl = true;
|
||||
private boolean omithtmlenvelope = false;
|
||||
private boolean useemptyelementtags = true;
|
||||
private boolean allowmultiwordattributes = true;
|
||||
private boolean allowhtmlinsideattributes = false;
|
||||
private boolean ignoreqe = true;
|
||||
private boolean namespacesaware = true;
|
||||
private String hyphenreplacement = "=";
|
||||
private String prunetags = "";
|
||||
private String booleanatts = CleanerProperties.BOOL_ATT_SELF;
|
||||
private String nodebyxpath = null;
|
||||
private boolean omitenvelope = false;
|
||||
|
||||
private String transform = null;
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public void setSrc(String src) {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
public void setDest(String dest) {
|
||||
this.dest = dest;
|
||||
}
|
||||
|
||||
public void setIncharset(String incharset) {
|
||||
this.incharset = incharset;
|
||||
}
|
||||
|
||||
public void setOutcharset(String outcharset) {
|
||||
this.outcharset = outcharset;
|
||||
}
|
||||
|
||||
public void setTaginfofile(String taginfofile) {
|
||||
this.taginfofile = taginfofile;
|
||||
}
|
||||
|
||||
public void setOutputtype(String outputtype) {
|
||||
this.outputtype = outputtype;
|
||||
}
|
||||
|
||||
public void setAdvancedxmlescape(boolean advancedxmlescape) {
|
||||
this.advancedxmlescape = advancedxmlescape;
|
||||
}
|
||||
|
||||
public void setTransrescharstoncr(boolean transrescharstoncr) {
|
||||
this.transrescharstoncr = transrescharstoncr;
|
||||
}
|
||||
|
||||
public void setUsecdata(boolean usecdata) {
|
||||
this.usecdata = usecdata;
|
||||
}
|
||||
|
||||
public void setSpecialentities(boolean specialentities) {
|
||||
this.specialentities = specialentities;
|
||||
}
|
||||
|
||||
public void setTransspecialentitiestoncr(boolean transspecialentitiestoncr) {
|
||||
this.transspecialentitiestoncr = transspecialentitiestoncr;
|
||||
}
|
||||
|
||||
public void setUnicodechars(boolean unicodechars) {
|
||||
this.unicodechars = unicodechars;
|
||||
}
|
||||
|
||||
public void setOmitunknowntags(boolean omitunknowntags) {
|
||||
this.omitunknowntags = omitunknowntags;
|
||||
}
|
||||
|
||||
public void setTreatunknowntagsascontent(boolean treatunknowntagsascontent) {
|
||||
this.treatunknowntagsascontent = treatunknowntagsascontent;
|
||||
}
|
||||
|
||||
public void setOmitdeprtags(boolean omitdeprtags) {
|
||||
this.omitdeprtags = omitdeprtags;
|
||||
}
|
||||
|
||||
|
||||
public void setTreatdeprtagsascontent(boolean treatdeprtagsascontent) {
|
||||
this.treatdeprtagsascontent = treatdeprtagsascontent;
|
||||
}
|
||||
|
||||
public void setOmitcomments(boolean omitcomments) {
|
||||
this.omitcomments = omitcomments;
|
||||
}
|
||||
|
||||
public void setOmitxmldecl(boolean omitxmldecl) {
|
||||
this.omitxmldecl = omitxmldecl;
|
||||
}
|
||||
|
||||
public void setOmitdoctypedecl(boolean omitdoctypedecl) {
|
||||
this.omitdoctypedecl = omitdoctypedecl;
|
||||
}
|
||||
|
||||
public void setOmithtmlenvelope(boolean omithtmlenvelope) {
|
||||
this.omithtmlenvelope = omithtmlenvelope;
|
||||
}
|
||||
|
||||
public void setUseemptyelementtags(boolean useemptyelementtags) {
|
||||
this.useemptyelementtags = useemptyelementtags;
|
||||
}
|
||||
|
||||
public void setAllowmultiwordattributes(boolean allowmultiwordattributes) {
|
||||
this.allowmultiwordattributes = allowmultiwordattributes;
|
||||
}
|
||||
|
||||
public void setAllowhtmlinsideattributes(boolean allowhtmlinsideattributes) {
|
||||
this.allowhtmlinsideattributes = allowhtmlinsideattributes;
|
||||
}
|
||||
|
||||
public void setIgnoreqe(boolean ignoreqe) {
|
||||
this.ignoreqe = ignoreqe;
|
||||
}
|
||||
|
||||
public void setNamespacesaware(boolean namespacesaware) {
|
||||
this.namespacesaware = namespacesaware;
|
||||
}
|
||||
|
||||
public void setHyphenreplacement(String hyphenreplacement) {
|
||||
this.hyphenreplacement = hyphenreplacement;
|
||||
}
|
||||
|
||||
public void setPrunetags(String prunetags) {
|
||||
this.prunetags = prunetags;
|
||||
}
|
||||
|
||||
public void setBooleanatts(String booleanatts) {
|
||||
this.booleanatts = booleanatts;
|
||||
}
|
||||
|
||||
public void setNodebyxpath(String nodebyxpath) {
|
||||
this.nodebyxpath = nodebyxpath;
|
||||
}
|
||||
|
||||
public void setOmitenvelope(boolean omitenvelope) {
|
||||
this.omitenvelope = omitenvelope;
|
||||
}
|
||||
|
||||
public void setTransform(String transform) {
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
public void addText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Ant task execution.
|
||||
* @throws BuildException
|
||||
*/
|
||||
public void execute() throws BuildException {
|
||||
HtmlCleaner cleaner;
|
||||
|
||||
if ( this.taginfofile != null ) {
|
||||
cleaner = new HtmlCleaner(new ConfigFileTagProvider(new File(this.taginfofile)));
|
||||
} else {
|
||||
cleaner = new HtmlCleaner();
|
||||
}
|
||||
|
||||
if (text == null && src == null) {
|
||||
throw new BuildException("Eather attribute 'src' or text body containing HTML must be specified!");
|
||||
}
|
||||
|
||||
CleanerProperties props = cleaner.getProperties();
|
||||
|
||||
props.setAdvancedXmlEscape(this.advancedxmlescape);
|
||||
props.setTransResCharsToNCR(this.transrescharstoncr);
|
||||
props.setUseCdataForScriptAndStyle(this.usecdata);
|
||||
props.setTranslateSpecialEntities(this.specialentities);
|
||||
props.setTransSpecialEntitiesToNCR(this.transspecialentitiestoncr);
|
||||
props.setRecognizeUnicodeChars(this.unicodechars);
|
||||
props.setOmitUnknownTags(this.omitunknowntags);
|
||||
props.setTreatUnknownTagsAsContent(this.treatunknowntagsascontent);
|
||||
props.setOmitDeprecatedTags(this.omitdeprtags);
|
||||
props.setTreatDeprecatedTagsAsContent(this.treatdeprtagsascontent);
|
||||
props.setOmitComments(this.omitcomments);
|
||||
props.setOmitXmlDeclaration(this.omitxmldecl);
|
||||
props.setOmitDoctypeDeclaration(this.omitdoctypedecl);
|
||||
props.setOmitHtmlEnvelope(this.omithtmlenvelope);
|
||||
props.setUseEmptyElementTags(this.useemptyelementtags);
|
||||
props.setAllowMultiWordAttributes(this.allowmultiwordattributes);
|
||||
props.setAllowHtmlInsideAttributes(this.allowhtmlinsideattributes);
|
||||
props.setIgnoreQuestAndExclam(this.ignoreqe);
|
||||
props.setNamespacesAware(this.namespacesaware);
|
||||
props.setHyphenReplacementInComment(this.hyphenreplacement);
|
||||
props.setPruneTags(this.prunetags);
|
||||
props.setBooleanAttributeValues(this.booleanatts);
|
||||
|
||||
// set cleaner transformation if specified in "transform" attribute
|
||||
// format of attribute is expected to be <transkey1>[=<transvalue1>]|<transkey2>[=<transvalue2>...
|
||||
// (separator is pipe character)
|
||||
if ( !Utils.isEmptyString(transform) ) {
|
||||
String[] transItems = Utils.tokenize(transform, "|");
|
||||
Map transInfos = new TreeMap();
|
||||
for (int i = 0; i < transItems.length; i++) {
|
||||
String item = transItems[i];
|
||||
int index = item.indexOf('=');
|
||||
String key = index <= 0 ? item : item.substring(0, index);
|
||||
String value = index <= 0 ? null : item.substring(index + 1);
|
||||
transInfos.put(key, value);
|
||||
}
|
||||
|
||||
CleanerTransformations transformations = new CleanerTransformations();
|
||||
Iterator iterator = transInfos.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String tag = (String) entry.getKey();
|
||||
String value = (String) entry.getValue();
|
||||
Utils.updateTagTransformations(transformations, tag, value);
|
||||
}
|
||||
cleaner.setTransformations(transformations);
|
||||
}
|
||||
|
||||
try {
|
||||
TagNode node;
|
||||
try {
|
||||
if ( src != null && (src.startsWith("http://") || src.startsWith("https://")) ) {
|
||||
node = cleaner.clean(new URL(src), incharset);
|
||||
} else if (src != null) {
|
||||
node = cleaner.clean(new File(src), incharset);
|
||||
} else {
|
||||
node = cleaner.clean(text);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new BuildException(e);
|
||||
}
|
||||
|
||||
// if user specifies XPath expresssion to choose node for serialization, then
|
||||
// try to evaluate XPath and look for first TagNode instance in the resulting array
|
||||
if ( nodebyxpath != null ) {
|
||||
final Object[] xpathResult = node.evaluateXPath(nodebyxpath);
|
||||
for (int i = 0; i < xpathResult.length; i++) {
|
||||
if ( xpathResult[i] instanceof TagNode ) {
|
||||
node = (TagNode) xpathResult[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream out;
|
||||
if ( dest == null || "".equals(dest.trim()) ) {
|
||||
out = System.out;
|
||||
} else {
|
||||
out = new FileOutputStream(dest);
|
||||
}
|
||||
|
||||
if ( "compact".equals(outputtype) ) {
|
||||
new CompactXmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
} else if ( "browser-compact".equals(outputtype) ) {
|
||||
new BrowserCompactXmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
} else if ( "pretty".equals(outputtype) ) {
|
||||
new PrettyXmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
} else if ( "htmlsimple".equals(outputtype) ) {
|
||||
new SimpleHtmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
} else if ( "htmlcompact".equals(outputtype) ) {
|
||||
new CompactHtmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
} else if ( "htmlpretty".equals(outputtype) ) {
|
||||
new PrettyHtmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
} else {
|
||||
new SimpleXmlSerializer(props).writeToStream(node, out, outcharset, omitenvelope);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new BuildException(e);
|
||||
} catch (XPatherException e) {
|
||||
throw new BuildException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* Marker interface denoting nodes of the document tree
|
||||
*/
|
||||
public interface HtmlNode {
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Abstract HTML serializer - contains common logic for descendants.</p>
|
||||
*/
|
||||
public abstract class HtmlSerializer extends Serializer {
|
||||
|
||||
protected HtmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
protected boolean isMinimizedTagSyntax(TagNode tagNode) {
|
||||
final TagInfo tagInfo = props.getTagInfoProvider().getTagInfo(tagNode.getName());
|
||||
return tagInfo != null && !tagNode.hasChildren() && tagInfo.isEmptyTag();
|
||||
}
|
||||
|
||||
protected boolean dontEscape(TagNode tagNode) {
|
||||
return isScriptOrStyle(tagNode);
|
||||
}
|
||||
|
||||
protected String escapeText(String s) {
|
||||
boolean recognizeUnicodeChars = props.isRecognizeUnicodeChars();
|
||||
boolean translateSpecialEntities = props.isTranslateSpecialEntities();
|
||||
|
||||
if (s != null) {
|
||||
int len = s.length();
|
||||
StringBuilder result = new StringBuilder(len);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
if (ch == '&') {
|
||||
if (i < len-2 && s.charAt(i+1) == '#') {
|
||||
boolean isHex = Character.toLowerCase(s.charAt(i+2)) == 'x';
|
||||
int charIndex = i + (isHex ? 3 : 2);
|
||||
int radix = isHex ? 16 : 10;
|
||||
String unicode = "";
|
||||
while (charIndex < len) {
|
||||
char currCh = s.charAt(charIndex);
|
||||
if (currCh == ';') {
|
||||
break;
|
||||
} else if (Utils.isValidInt(unicode + currCh, radix)) {
|
||||
unicode += currCh;
|
||||
charIndex++;
|
||||
} else {
|
||||
charIndex--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.isValidInt(unicode, radix)) {
|
||||
char unicodeChar = (char)Integer.parseInt(unicode, radix);
|
||||
if ( !Utils.isValidXmlChar(unicodeChar) ) {
|
||||
i = charIndex;
|
||||
} else if ( !Utils.isReservedXmlChar(unicodeChar) ) {
|
||||
result.append( recognizeUnicodeChars ? String.valueOf(unicodeChar) : "&#" + unicode + ";" );
|
||||
i = charIndex;
|
||||
} else {
|
||||
i = charIndex;
|
||||
result.append("&#" + unicode + ";");
|
||||
}
|
||||
} else {
|
||||
result.append(props.transResCharsToNCR ? "&#" + (int)'&' + ";" : "&");
|
||||
}
|
||||
} else {
|
||||
// get minimal following sequence required to recognize some special entitiy
|
||||
String seq = s.substring(i, i + Math.min(SpecialEntity.getMaxEntityLength() + 2, len - i));
|
||||
int semiIndex = seq.indexOf(';');
|
||||
if (semiIndex > 0) {
|
||||
String entityKey = seq.substring(1, semiIndex);
|
||||
SpecialEntity entity = SpecialEntity.getEntity(entityKey);
|
||||
if (entity != null) {
|
||||
if (translateSpecialEntities) {
|
||||
result.append(props.isTransSpecialEntitiesToNCR() ? entity.getDecimalNCR() : entity.getCharacter());
|
||||
} else {
|
||||
result.append(entity.getEscapedValue());
|
||||
}
|
||||
|
||||
i += entityKey.length() + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String sub = s.substring(i);
|
||||
boolean isReservedSeq = false;
|
||||
for (Map.Entry<Character, String> entry: Utils.RESERVED_XML_CHARS.entrySet()) {
|
||||
seq = entry.getValue();
|
||||
if ( sub.startsWith(seq) ) {
|
||||
result.append( props.transResCharsToNCR ? "&#" + (int)entry.getKey() + ";" : seq );
|
||||
i += seq.length() - 1;
|
||||
isReservedSeq = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isReservedSeq) {
|
||||
result.append( props.transResCharsToNCR ? "&#" + (int)'&' + ";" : "&" );
|
||||
}
|
||||
}
|
||||
} else if (Utils.isReservedXmlChar(ch)) {
|
||||
result.append( props.transResCharsToNCR ? "&#" + (int)ch + ";" : ch );
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void serializeOpenTag(TagNode tagNode, Writer writer, boolean newLine) throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean nsAware = props.isNamespacesAware();
|
||||
|
||||
if (!nsAware && Utils.getXmlNSPrefix(tagName) != null ) {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
|
||||
writer.write("<" + tagName);
|
||||
for (Map.Entry<String, String> entry: tagNode.getAttributes().entrySet()) {
|
||||
String attName = entry.getKey();
|
||||
if (!nsAware && Utils.getXmlNSPrefix(attName) != null ) {
|
||||
attName = Utils.getXmlName(attName);
|
||||
}
|
||||
writer.write(" " + attName + "=\"" + escapeText(entry.getValue()) + "\"");
|
||||
}
|
||||
|
||||
if (nsAware) {
|
||||
Map<String, String> nsDeclarations = tagNode.getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (Map.Entry<String, String> entry: nsDeclarations.entrySet()) {
|
||||
String prefix = entry.getKey();
|
||||
String att = "xmlns";
|
||||
if (prefix.length() > 0) {
|
||||
att += ":" + prefix;
|
||||
}
|
||||
writer.write(" " + att + "=\"" + escapeText(entry.getValue()) + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isMinimizedTagSyntax(tagNode) ) {
|
||||
writer.write(" />");
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
} else {
|
||||
writer.write(">");
|
||||
}
|
||||
}
|
||||
|
||||
protected void serializeEndTag(TagNode tagNode, Writer writer, boolean newLine) throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.getXmlNSPrefix(tagName) != null && !props.isNamespacesAware()) {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
|
||||
writer.write( "</" + tagName + ">" );
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,813 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Main HTML tokenizer.
|
||||
* <p>It's task is to parse HTML and produce list of valid tokens:
|
||||
* open tag tokens, end tag tokens, contents (text) and comments.
|
||||
* As soon as new item is added to token list, cleaner is invoked
|
||||
* to clean current list at the end.</p>
|
||||
*/
|
||||
abstract public class HtmlTokenizer {
|
||||
|
||||
private final static int WORKING_BUFFER_SIZE = 1024;
|
||||
|
||||
private BufferedReader _reader;
|
||||
private char[] _working = new char[WORKING_BUFFER_SIZE];
|
||||
|
||||
private transient int _pos = 0;
|
||||
private transient int _len = -1;
|
||||
|
||||
private transient char _saved[] = new char[512];
|
||||
private transient int _savedLen = 0;
|
||||
|
||||
private transient DoctypeToken _docType = null;
|
||||
private transient TagToken _currentTagToken = null;
|
||||
private transient List<BaseToken> _tokenList = new ArrayList<BaseToken>();
|
||||
|
||||
private boolean _asExpected = true;
|
||||
|
||||
private boolean _isScriptContext = false;
|
||||
|
||||
private CleanerProperties props;
|
||||
|
||||
private boolean isOmitUnknownTags;
|
||||
private boolean isTreatUnknownTagsAsContent;
|
||||
private boolean isOmitDeprecatedTags;
|
||||
private boolean isTreatDeprecatedTagsAsContent;
|
||||
private boolean isNamespacesAware;
|
||||
private boolean isOmitComments;
|
||||
private boolean isAllowMultiWordAttributes;
|
||||
private boolean isAllowHtmlInsideAttributes;
|
||||
|
||||
private CleanerTransformations transformations;
|
||||
private ITagInfoProvider tagInfoProvider;
|
||||
|
||||
private StringBuilder commonStr = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Constructor - cretes instance of the parser with specified content.
|
||||
*
|
||||
* @param reader
|
||||
* @param props
|
||||
* @param transformations
|
||||
* @param tagInfoProvider
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public HtmlTokenizer(Reader reader, CleanerProperties props, CleanerTransformations transformations, ITagInfoProvider tagInfoProvider) throws IOException {
|
||||
this._reader = new BufferedReader(reader);
|
||||
this.props = props;
|
||||
this.isOmitUnknownTags = props.isOmitUnknownTags();
|
||||
this.isTreatUnknownTagsAsContent = props.isTreatUnknownTagsAsContent();
|
||||
this.isOmitDeprecatedTags = props.isOmitDeprecatedTags();
|
||||
this.isTreatDeprecatedTagsAsContent = props.isTreatDeprecatedTagsAsContent();
|
||||
this.isNamespacesAware = props.isNamespacesAware();
|
||||
this.isOmitComments = props.isOmitComments();
|
||||
this.isAllowMultiWordAttributes = props.isAllowMultiWordAttributes();
|
||||
this.isAllowHtmlInsideAttributes = props.isAllowHtmlInsideAttributes();
|
||||
this.transformations = transformations;
|
||||
this.tagInfoProvider = tagInfoProvider;
|
||||
}
|
||||
|
||||
private void addToken(BaseToken token) {
|
||||
_tokenList.add(token);
|
||||
makeTree(_tokenList);
|
||||
}
|
||||
|
||||
abstract void makeTree(List<BaseToken> tokenList);
|
||||
|
||||
abstract TagNode createTagNode(String name);
|
||||
|
||||
private void readIfNeeded(int neededChars) throws IOException {
|
||||
if (_len == -1 && _pos + neededChars >= WORKING_BUFFER_SIZE) {
|
||||
int numToCopy = WORKING_BUFFER_SIZE - _pos;
|
||||
System.arraycopy(_working, _pos, _working, 0, numToCopy);
|
||||
_pos = 0;
|
||||
|
||||
int expected = WORKING_BUFFER_SIZE - numToCopy;
|
||||
int size = 0;
|
||||
int charsRead;
|
||||
int offset = numToCopy;
|
||||
do {
|
||||
charsRead = _reader.read(_working, offset, expected);
|
||||
if (charsRead >= 0) {
|
||||
size += charsRead;
|
||||
offset += charsRead;
|
||||
expected -= charsRead;
|
||||
}
|
||||
} while (charsRead >= 0 && expected > 0);
|
||||
|
||||
if (expected > 0) {
|
||||
_len = size + numToCopy;
|
||||
}
|
||||
|
||||
// convert invalid XML characters to spaces
|
||||
for (int i = 0; i < (_len >= 0 ? _len : WORKING_BUFFER_SIZE); i++) {
|
||||
int ch = _working[i];
|
||||
if (ch >= 1 && ch <= 32 && ch != 10 && ch != 13) {
|
||||
_working[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<BaseToken> getTokenList() {
|
||||
return this._tokenList;
|
||||
}
|
||||
|
||||
private void go() throws IOException {
|
||||
_pos++;
|
||||
readIfNeeded(0);
|
||||
}
|
||||
|
||||
private void go(int step) throws IOException {
|
||||
_pos += step;
|
||||
readIfNeeded(step - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if content starts with specified value at the current position.
|
||||
* @param value
|
||||
* @return true if starts with specified value, false otherwise.
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean startsWith(String value) throws IOException {
|
||||
int valueLen = value.length();
|
||||
readIfNeeded(valueLen);
|
||||
if (_len >= 0 && _pos + valueLen > _len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueLen; i++) {
|
||||
char ch1 = Character.toLowerCase( value.charAt(i) );
|
||||
char ch2 = Character.toLowerCase( _working[_pos + i] );
|
||||
if (ch1 != ch2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean startsWithSimple(String value) throws IOException {
|
||||
int valueLen = value.length();
|
||||
readIfNeeded(valueLen);
|
||||
if (_len >= 0 && _pos + valueLen > _len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueLen; i++) {
|
||||
if (value.charAt(i) != _working[_pos + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at specified position is whitespace.
|
||||
* @param position
|
||||
* @return true is whitespace, false otherwise.
|
||||
*/
|
||||
private boolean isWhitespace(int position) {
|
||||
if (_len >= 0 && position >= _len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Character.isWhitespace( _working[position] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position is whitespace.
|
||||
* @return true is whitespace, false otherwise.
|
||||
*/
|
||||
private boolean isWhitespace() {
|
||||
return isWhitespace(_pos);
|
||||
}
|
||||
|
||||
private boolean isWhitespaceSafe() {
|
||||
return Character.isWhitespace( _working[_pos] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at specified position is equal to specified char.
|
||||
* @param position
|
||||
* @param ch
|
||||
* @return true is equals, false otherwise.
|
||||
*/
|
||||
private boolean isChar(int position, char ch) {
|
||||
if (_len >= 0 && position >= _len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Character.toLowerCase(ch) == Character.toLowerCase(_working[position]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position is equal to specified char.
|
||||
* @param ch
|
||||
* @return true is equal, false otherwise.
|
||||
*/
|
||||
private boolean isChar(char ch) {
|
||||
return isChar(_pos, ch);
|
||||
}
|
||||
|
||||
private boolean isCharSimple(char ch) {
|
||||
return (_len < 0 || _pos < _len) && (ch == _working[_pos]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current character to be read, but first it must be checked if it exists.
|
||||
* This method is made for performance reasons to be used instead of isChar(...).
|
||||
*/
|
||||
private char getCurrentChar() {
|
||||
return _working[_pos];
|
||||
}
|
||||
|
||||
private boolean isCharEquals(char ch) {
|
||||
return _working[_pos] == ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at specified position can be identifier start.
|
||||
* @param position
|
||||
* @return true is may be identifier start, false otherwise.
|
||||
*/
|
||||
private boolean isIdentifierStartChar(int position) {
|
||||
if (_len >= 0 && position >= _len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char ch = _working[position];
|
||||
return Character.isUnicodeIdentifierStart(ch) || ch == '_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position can be identifier start.
|
||||
* @return true is may be identifier start, false otherwise.
|
||||
*/
|
||||
private boolean isIdentifierStartChar() {
|
||||
return isIdentifierStartChar(_pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position can be identifier part.
|
||||
* @return true is may be identifier part, false otherwise.
|
||||
*/
|
||||
private boolean isIdentifierChar() {
|
||||
if (_len >= 0 && _pos >= _len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char ch = _working[_pos];
|
||||
return Character.isUnicodeIdentifierStart(ch) || Character.isDigit(ch) || Utils.isIdentifierHelperChar(ch);
|
||||
}
|
||||
|
||||
private boolean isValidXmlChar() {
|
||||
return isAllRead() || Utils.isValidXmlChar(_working[_pos]);
|
||||
}
|
||||
|
||||
private boolean isValidXmlCharSafe() {
|
||||
return Utils.isValidXmlChar(_working[_pos]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if end of the content is reached.
|
||||
*/
|
||||
private boolean isAllRead() {
|
||||
return _len >= 0 && _pos >= _len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves specified character to the temporary buffer.
|
||||
* @param ch
|
||||
*/
|
||||
private void save(char ch) {
|
||||
if (_savedLen >= _saved.length) {
|
||||
char newSaved[] = new char[_saved.length + 512];
|
||||
System.arraycopy(_saved, 0, newSaved, 0, _saved.length);
|
||||
_saved = newSaved;
|
||||
}
|
||||
_saved[_savedLen++] = ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves character at current runtime position to the temporary buffer.
|
||||
*/
|
||||
private void saveCurrent() {
|
||||
if (!isAllRead()) {
|
||||
save( _working[_pos] );
|
||||
}
|
||||
}
|
||||
|
||||
private void saveCurrentSafe() {
|
||||
save( _working[_pos] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves specified number of characters at current runtime position to the temporary buffer.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void saveCurrent(int size) throws IOException {
|
||||
readIfNeeded(size);
|
||||
int pos = _pos;
|
||||
while ( !isAllRead() && (size > 0) ) {
|
||||
save( _working[pos] );
|
||||
pos++;
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips whitespaces at current position and moves foreward until
|
||||
* non-whitespace character is found or the end of content is reached.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void skipWhitespaces() throws IOException {
|
||||
while ( !isAllRead() && isWhitespaceSafe() ) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addSavedAsContent() {
|
||||
if (_savedLen > 0) {
|
||||
addToken(new ContentNode(_saved, _savedLen));
|
||||
_savedLen = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts parsing HTML.
|
||||
* @throws IOException
|
||||
*/
|
||||
void start() throws IOException {
|
||||
// initialize runtime values
|
||||
_currentTagToken = null;
|
||||
_tokenList.clear();
|
||||
_asExpected = true;
|
||||
_isScriptContext = false;
|
||||
|
||||
boolean isLateForDoctype = false;
|
||||
|
||||
this._pos = WORKING_BUFFER_SIZE;
|
||||
readIfNeeded(0);
|
||||
|
||||
boolean isScriptEmpty = true;
|
||||
|
||||
while ( !isAllRead() ) {
|
||||
// resets all the runtime values
|
||||
_savedLen = 0;
|
||||
_currentTagToken = null;
|
||||
_asExpected = true;
|
||||
|
||||
// this is enough for making decision
|
||||
readIfNeeded(10);
|
||||
|
||||
if (_isScriptContext) {
|
||||
if ( startsWith("</script") && (isWhitespace(_pos + 8) || isChar(_pos + 8, '>')) ) {
|
||||
tagEnd();
|
||||
} else if ( isScriptEmpty && startsWithSimple("<!--") ) {
|
||||
comment();
|
||||
} else {
|
||||
boolean isTokenAdded = content();
|
||||
if (isScriptEmpty && isTokenAdded) {
|
||||
final BaseToken lastToken = _tokenList.get(_tokenList.size() - 1);
|
||||
if (lastToken != null) {
|
||||
final String lastTokenAsString = lastToken.toString();
|
||||
if (lastTokenAsString != null && lastTokenAsString.trim().length() > 0) {
|
||||
isScriptEmpty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_isScriptContext) {
|
||||
isScriptEmpty = true;
|
||||
}
|
||||
} else {
|
||||
if ( startsWith("<!doctype") ) {
|
||||
if ( !isLateForDoctype ) {
|
||||
doctype();
|
||||
isLateForDoctype = true;
|
||||
} else {
|
||||
ignoreUntil('<');
|
||||
}
|
||||
} else if ( startsWithSimple("</") && isIdentifierStartChar(_pos + 2) ) {
|
||||
isLateForDoctype = true;
|
||||
tagEnd();
|
||||
} else if ( startsWithSimple("<!--") ) {
|
||||
comment();
|
||||
} else if ( startsWithSimple("<") && isIdentifierStartChar(_pos + 1) ) {
|
||||
isLateForDoctype = true;
|
||||
tagStart();
|
||||
} else if ( props.isIgnoreQuestAndExclam() && (startsWithSimple("<!") || startsWithSimple("<?")) ) {
|
||||
ignoreUntil('>');
|
||||
if (isCharSimple('>')) {
|
||||
go();
|
||||
}
|
||||
} else {
|
||||
content();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_reader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified tag name is one of the reserved tags: HTML, HEAD or BODY
|
||||
* @param tagName
|
||||
* @return
|
||||
*/
|
||||
private boolean isReservedTag(String tagName) {
|
||||
tagName = tagName.toLowerCase();
|
||||
return "html".equals(tagName) || "head".equals(tagName) || "body".equals(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses start of the tag.
|
||||
* It expects that current position is at the "<" after which
|
||||
* the tag's name follows.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void tagStart() throws IOException {
|
||||
saveCurrent();
|
||||
go();
|
||||
|
||||
if ( isAllRead() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
String tagName = identifier();
|
||||
|
||||
TagTransformation tagTransformation = null;
|
||||
if (transformations != null && transformations.hasTransformationForTag(tagName)) {
|
||||
tagTransformation = transformations.getTransformation(tagName);
|
||||
if (tagTransformation != null) {
|
||||
tagName = tagTransformation.getDestTag();
|
||||
}
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
TagInfo tagInfo = tagInfoProvider.getTagInfo(tagName);
|
||||
if ( (tagInfo == null && !isOmitUnknownTags && isTreatUnknownTagsAsContent && !isReservedTag(tagName)) ||
|
||||
(tagInfo != null && tagInfo.isDeprecated() && !isOmitDeprecatedTags && isTreatDeprecatedTagsAsContent) ) {
|
||||
content();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TagNode tagNode = createTagNode(tagName);
|
||||
_currentTagToken = tagNode;
|
||||
|
||||
if (_asExpected) {
|
||||
skipWhitespaces();
|
||||
tagAttributes();
|
||||
|
||||
if (tagName != null) {
|
||||
if (tagTransformation != null) {
|
||||
tagNode.transformAttributes(tagTransformation);
|
||||
}
|
||||
addToken(_currentTagToken);
|
||||
}
|
||||
|
||||
if ( isCharSimple('>') ) {
|
||||
go();
|
||||
if ( "script".equalsIgnoreCase(tagName) ) {
|
||||
_isScriptContext = true;
|
||||
}
|
||||
} else if ( startsWithSimple("/>") ) {
|
||||
go(2);
|
||||
if ( "script".equalsIgnoreCase(tagName) ) {
|
||||
addToken( new EndTagToken(tagName) );
|
||||
}
|
||||
}
|
||||
|
||||
_currentTagToken = null;
|
||||
} else {
|
||||
addSavedAsContent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses end of the tag.
|
||||
* It expects that current position is at the "<" after which
|
||||
* "/" and the tag's name follows.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void tagEnd() throws IOException {
|
||||
saveCurrent(2);
|
||||
go(2);
|
||||
|
||||
if ( isAllRead() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
String tagName = identifier();
|
||||
if (transformations != null && transformations.hasTransformationForTag(tagName)) {
|
||||
TagTransformation tagTransformation = transformations.getTransformation(tagName);
|
||||
if (tagTransformation != null) {
|
||||
tagName = tagTransformation.getDestTag();
|
||||
}
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
TagInfo tagInfo = tagInfoProvider.getTagInfo(tagName);
|
||||
if ( (tagInfo == null && !isOmitUnknownTags && isTreatUnknownTagsAsContent && !isReservedTag(tagName)) ||
|
||||
(tagInfo != null && tagInfo.isDeprecated() && !isOmitDeprecatedTags && isTreatDeprecatedTagsAsContent) ) {
|
||||
content();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_currentTagToken = new EndTagToken(tagName);
|
||||
|
||||
if (_asExpected) {
|
||||
skipWhitespaces();
|
||||
tagAttributes();
|
||||
|
||||
if (tagName != null) {
|
||||
addToken(_currentTagToken);
|
||||
}
|
||||
|
||||
if ( isCharSimple('>') ) {
|
||||
go();
|
||||
}
|
||||
|
||||
if ( "script".equalsIgnoreCase(tagName) ) {
|
||||
_isScriptContext = false;
|
||||
}
|
||||
|
||||
_currentTagToken = null;
|
||||
} else {
|
||||
addSavedAsContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an identifier from the current position.
|
||||
* @throws IOException
|
||||
*/
|
||||
private String identifier() throws IOException {
|
||||
_asExpected = true;
|
||||
|
||||
if ( !isIdentifierStartChar() ) {
|
||||
_asExpected = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
commonStr.delete(0, commonStr.length());
|
||||
|
||||
while ( !isAllRead() && isIdentifierChar() ) {
|
||||
saveCurrentSafe();
|
||||
commonStr.append( _working[_pos] );
|
||||
go();
|
||||
}
|
||||
|
||||
// strip invalid characters from the end
|
||||
while ( commonStr.length() > 0 && Utils.isIdentifierHelperChar(commonStr.charAt(commonStr.length() - 1)) ) {
|
||||
commonStr.deleteCharAt( commonStr.length() - 1 );
|
||||
}
|
||||
|
||||
if ( commonStr.length() == 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String id = commonStr.toString();
|
||||
|
||||
int columnIndex = id.indexOf(':');
|
||||
if (columnIndex >= 0) {
|
||||
String prefix = id.substring(0, columnIndex);
|
||||
String suffix = id.substring(columnIndex + 1);
|
||||
int nextColumnIndex = suffix.indexOf(':');
|
||||
if (nextColumnIndex >= 0) {
|
||||
suffix = suffix.substring(0, nextColumnIndex);
|
||||
}
|
||||
id = isNamespacesAware ? (prefix + ":" + suffix) : suffix;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses list tag attributes from the current position.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void tagAttributes() throws IOException {
|
||||
while( !isAllRead() && _asExpected && !isCharSimple('>') && !startsWithSimple("/>") ) {
|
||||
skipWhitespaces();
|
||||
String attName = identifier();
|
||||
|
||||
if (!_asExpected) {
|
||||
if ( !isCharSimple('<') && !isCharSimple('>') && !startsWithSimple("/>") ) {
|
||||
if (isValidXmlChar()) {
|
||||
saveCurrent();
|
||||
}
|
||||
go();
|
||||
}
|
||||
|
||||
if (!isCharSimple('<')) {
|
||||
_asExpected = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
String attValue;
|
||||
|
||||
skipWhitespaces();
|
||||
if ( isCharSimple('=') ) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
attValue = attributeValue();
|
||||
} else if (CleanerProperties.BOOL_ATT_EMPTY.equals(props.booleanAttributeValues)) {
|
||||
attValue = "";
|
||||
} else if (CleanerProperties.BOOL_ATT_TRUE.equals(props.booleanAttributeValues)) {
|
||||
attValue = "true";
|
||||
} else {
|
||||
attValue = attName;
|
||||
}
|
||||
|
||||
if (_asExpected) {
|
||||
_currentTagToken.setAttribute(attName, attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single tag attribute - it is expected to be in one of the forms:
|
||||
* name=value
|
||||
* name="value"
|
||||
* name='value'
|
||||
* name
|
||||
* @throws IOException
|
||||
*/
|
||||
private String attributeValue() throws IOException {
|
||||
skipWhitespaces();
|
||||
|
||||
if ( isCharSimple('<') || isCharSimple('>') || startsWithSimple("/>") ) {
|
||||
return "";
|
||||
}
|
||||
|
||||
boolean isQuoteMode = false;
|
||||
boolean isAposMode = false;
|
||||
|
||||
commonStr.delete(0, commonStr.length());
|
||||
|
||||
if ( isCharSimple('\'') ) {
|
||||
isAposMode = true;
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
} else if ( isCharSimple('\"') ) {
|
||||
isQuoteMode = true;
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
}
|
||||
|
||||
while ( !isAllRead() &&
|
||||
( ((isAposMode && !isCharEquals('\'') || isQuoteMode && !isCharEquals('\"')) && (isAllowHtmlInsideAttributes || !isCharEquals('>') && !isCharEquals('<')) && (isAllowMultiWordAttributes || !isWhitespaceSafe())) ||
|
||||
(!isAposMode && !isQuoteMode && !isWhitespaceSafe() && !isCharEquals('>') && !isCharEquals('<'))
|
||||
)
|
||||
) {
|
||||
if (isValidXmlCharSafe()) {
|
||||
commonStr.append( _working[_pos] );
|
||||
saveCurrentSafe();
|
||||
}
|
||||
go();
|
||||
}
|
||||
|
||||
if ( isCharSimple('\'') && isAposMode ) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
} else if ( isCharSimple('\"') && isQuoteMode ) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
}
|
||||
|
||||
|
||||
return commonStr.toString();
|
||||
}
|
||||
|
||||
private boolean content() throws IOException {
|
||||
while ( !isAllRead() ) {
|
||||
if (isValidXmlCharSafe()) {
|
||||
saveCurrentSafe();
|
||||
}
|
||||
go();
|
||||
|
||||
if ( isCharSimple('<') ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return addSavedAsContent();
|
||||
}
|
||||
|
||||
private void ignoreUntil(char ch) throws IOException {
|
||||
while ( !isAllRead() ) {
|
||||
go();
|
||||
if ( isChar(ch) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void comment() throws IOException {
|
||||
go(4);
|
||||
while ( !isAllRead() && !startsWithSimple("-->") ) {
|
||||
if (isValidXmlCharSafe()) {
|
||||
saveCurrentSafe();
|
||||
}
|
||||
go();
|
||||
}
|
||||
|
||||
if (startsWithSimple("-->")) {
|
||||
go(3);
|
||||
}
|
||||
|
||||
if (_savedLen > 0) {
|
||||
if (!isOmitComments) {
|
||||
String hyphenRepl = props.getHyphenReplacementInComment();
|
||||
String comment = new String(_saved, 0, _savedLen).replaceAll("--", hyphenRepl + hyphenRepl);
|
||||
|
||||
if ( comment.length() > 0 && comment.charAt(0) == '-' ) {
|
||||
comment = hyphenRepl + comment.substring(1);
|
||||
}
|
||||
int len = comment.length();
|
||||
if ( len > 0 && comment.charAt(len - 1) == '-' ) {
|
||||
comment = comment.substring(0, len - 1) + hyphenRepl;
|
||||
}
|
||||
|
||||
addToken( new CommentNode(comment) );
|
||||
}
|
||||
_savedLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void doctype() throws IOException {
|
||||
go(9);
|
||||
|
||||
skipWhitespaces();
|
||||
String part1 = identifier();
|
||||
skipWhitespaces();
|
||||
String part2 = identifier();
|
||||
skipWhitespaces();
|
||||
String part3 = attributeValue();
|
||||
skipWhitespaces();
|
||||
String part4 = attributeValue();
|
||||
|
||||
ignoreUntil('<');
|
||||
|
||||
_docType = new DoctypeToken(part1, part2, part3, part4);
|
||||
}
|
||||
|
||||
public DoctypeToken getDocType() {
|
||||
return _docType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides set of TagInfo instances. The instance of this interface is used as a
|
||||
* collection of tag definitions used in cleanup process. Implementing this interface
|
||||
* desired behaviour of cleaner can be achived.<br/>
|
||||
* In most cases implementation will be or contain a kind of Map.
|
||||
* </p>
|
||||
*/
|
||||
public interface ITagInfoProvider {
|
||||
|
||||
public TagInfo getTagInfo(String tagName);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package org.htmlcleaner;
|
||||
|
||||
import org.jdom.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>JDom serializer - creates xml JDom instance out of the TagNode.</p>
|
||||
*/
|
||||
public class JDomSerializer {
|
||||
|
||||
private DefaultJDOMFactory factory;
|
||||
|
||||
protected CleanerProperties props;
|
||||
protected boolean escapeXml = true;
|
||||
|
||||
public JDomSerializer(CleanerProperties props, boolean escapeXml) {
|
||||
this.props = props;
|
||||
this.escapeXml = escapeXml;
|
||||
}
|
||||
|
||||
public JDomSerializer(CleanerProperties props) {
|
||||
this(props, true);
|
||||
}
|
||||
|
||||
public Document createJDom(TagNode rootNode) {
|
||||
this.factory = new DefaultJDOMFactory();
|
||||
Element rootElement = createElement(rootNode);
|
||||
Document document = this.factory.document(rootElement);
|
||||
|
||||
setAttributes(rootNode, rootElement);
|
||||
|
||||
createSubnodes(rootElement, rootNode.getChildren());
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private Element createElement(TagNode node) {
|
||||
String name = node.getName();
|
||||
boolean nsAware = props.isNamespacesAware();
|
||||
String prefix = Utils.getXmlNSPrefix(name);
|
||||
Map<String, String> nsDeclarations = node.getNamespaceDeclarations();
|
||||
String nsURI = null;
|
||||
if (prefix != null) {
|
||||
name = Utils.getXmlName(name);
|
||||
if (nsAware) {
|
||||
if (nsDeclarations != null) {
|
||||
nsURI = nsDeclarations.get(prefix);
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = node.getNamespaceURIOnPath(prefix);
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = prefix;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nsAware) {
|
||||
if (nsDeclarations != null) {
|
||||
nsURI = nsDeclarations.get("");
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = node.getNamespaceURIOnPath(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Element element;
|
||||
if (nsAware && nsURI != null) {
|
||||
Namespace ns = prefix == null ? Namespace.getNamespace(nsURI) : Namespace.getNamespace(prefix, nsURI);
|
||||
element = factory.element(name, ns);
|
||||
} else {
|
||||
element = factory.element(name);
|
||||
}
|
||||
|
||||
if (nsAware) {
|
||||
defineNamespaceDeclarations(node, element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private void defineNamespaceDeclarations(TagNode node, Element element) {
|
||||
Map<String, String> nsDeclarations = node.getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (Map.Entry<String, String> nsEntry: nsDeclarations.entrySet()) {
|
||||
String nsPrefix = nsEntry.getKey();
|
||||
String nsURI = nsEntry.getValue();
|
||||
Namespace ns = nsPrefix == null || "".equals(nsPrefix) ? Namespace.getNamespace(nsURI) : Namespace.getNamespace(nsPrefix, nsURI);
|
||||
element.addNamespaceDeclaration(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setAttributes(TagNode node, Element element) {
|
||||
for (Map.Entry<String, String> entry: node.getAttributes().entrySet()) {
|
||||
String attrName = entry.getKey();
|
||||
String attrValue = entry.getValue();
|
||||
if (escapeXml) {
|
||||
attrValue = Utils.escapeXml(attrValue, props, true);
|
||||
}
|
||||
String attPrefix = Utils.getXmlNSPrefix(attrName);
|
||||
Namespace ns = null;
|
||||
if (attPrefix != null) {
|
||||
attrName = Utils.getXmlName(attrName);
|
||||
if (props.isNamespacesAware()) {
|
||||
String nsURI = node.getNamespaceURIOnPath(attPrefix);
|
||||
if (nsURI == null) {
|
||||
nsURI = attPrefix;
|
||||
}
|
||||
ns = Namespace.getNamespace(attPrefix, nsURI);
|
||||
}
|
||||
}
|
||||
if (ns == null) {
|
||||
element.setAttribute(attrName, attrValue);
|
||||
} else {
|
||||
element.setAttribute(attrName, attrValue, ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createSubnodes(Element element, List tagChildren) {
|
||||
if (tagChildren != null) {
|
||||
Iterator it = tagChildren.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object item = it.next();
|
||||
if (item instanceof CommentNode) {
|
||||
CommentNode commentNode = (CommentNode) item;
|
||||
Comment comment = factory.comment( commentNode.getContent().toString() );
|
||||
element.addContent(comment);
|
||||
} else if (item instanceof ContentNode) {
|
||||
String nodeName = element.getName();
|
||||
String content = item.toString();
|
||||
boolean specialCase = props.isUseCdataForScriptAndStyle() &&
|
||||
("script".equalsIgnoreCase(nodeName) || "style".equalsIgnoreCase(nodeName));
|
||||
if (escapeXml && !specialCase) {
|
||||
content = Utils.escapeXml(content, props, true);
|
||||
}
|
||||
Text text = specialCase ? factory.cdata(content) : factory.text(content);
|
||||
element.addContent(text);
|
||||
} else if (item instanceof TagNode) {
|
||||
TagNode subTagNode = (TagNode) item;
|
||||
Element subelement = createElement(subTagNode);
|
||||
|
||||
setAttributes(subTagNode, subelement);
|
||||
|
||||
// recursively create subnodes
|
||||
createSubnodes(subelement, subTagNode.getChildren());
|
||||
|
||||
element.addContent(subelement);
|
||||
} else if (item instanceof List) {
|
||||
List sublist = (List) item;
|
||||
createSubnodes(element, sublist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Pretty HTML serializer - creates resulting HTML with indenting lines.</p>
|
||||
*/
|
||||
public class PrettyHtmlSerializer extends HtmlSerializer {
|
||||
|
||||
private static final String DEFAULT_INDENTATION_STRING = "\t";
|
||||
|
||||
private String indentString = DEFAULT_INDENTATION_STRING;
|
||||
private List<String> indents = new ArrayList<String>();
|
||||
|
||||
public PrettyHtmlSerializer(CleanerProperties props) {
|
||||
this(props, DEFAULT_INDENTATION_STRING);
|
||||
}
|
||||
|
||||
public PrettyHtmlSerializer(CleanerProperties props, String indentString) {
|
||||
super(props);
|
||||
this.indentString = indentString;
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
serializePrettyHtml(tagNode, writer, 0, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param level
|
||||
* @return Appropriate indentation for the specified depth.
|
||||
*/
|
||||
private synchronized String getIndent(int level) {
|
||||
int size = indents.size();
|
||||
if (size <= level) {
|
||||
String prevIndent = size == 0 ? null : indents.get(size - 1);
|
||||
for (int i = size; i <= level; i++) {
|
||||
String currIndent = prevIndent == null ? "" : prevIndent + indentString;
|
||||
indents.add(currIndent);
|
||||
prevIndent = currIndent;
|
||||
}
|
||||
}
|
||||
|
||||
return indents.get(level);
|
||||
}
|
||||
|
||||
private String getIndentedText(String content, int level) {
|
||||
String indent = getIndent(level);
|
||||
StringBuilder result = new StringBuilder( content.length() );
|
||||
StringTokenizer tokenizer = new StringTokenizer(content, "\n\r");
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String line = tokenizer.nextToken().trim();
|
||||
if (!"".equals(line)) {
|
||||
result.append(indent).append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String getSingleLineOfChildren(List children) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
Iterator childrenIt = children.iterator();
|
||||
boolean isFirst = true;
|
||||
|
||||
while (childrenIt.hasNext()) {
|
||||
Object child = childrenIt.next();
|
||||
|
||||
if ( !(child instanceof ContentNode) ) {
|
||||
return null;
|
||||
} else {
|
||||
String content = child.toString();
|
||||
|
||||
// if first item trims it from left
|
||||
if (isFirst) {
|
||||
content = Utils.ltrim(content);
|
||||
}
|
||||
|
||||
// if last item trims it from right
|
||||
if (!childrenIt.hasNext()) {
|
||||
content = Utils.rtrim(content);
|
||||
}
|
||||
|
||||
if ( content.indexOf("\n") >= 0 || content.indexOf("\r") >= 0 ) {
|
||||
return null;
|
||||
}
|
||||
result.append(content);
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected void serializePrettyHtml(TagNode tagNode, Writer writer, int level, boolean isPreserveWhitespaces, boolean isLastNewLine) throws IOException {
|
||||
List tagChildren = tagNode.getChildren();
|
||||
String tagName = tagNode.getName();
|
||||
boolean isHeadlessNode = Utils.isEmptyString(tagName);
|
||||
String indent = isHeadlessNode ? "" : getIndent(level);
|
||||
|
||||
if (!isPreserveWhitespaces) {
|
||||
if (!isLastNewLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.write(indent);
|
||||
}
|
||||
serializeOpenTag(tagNode, writer, true);
|
||||
|
||||
boolean preserveWhitespaces = isPreserveWhitespaces || "pre".equalsIgnoreCase(tagName);
|
||||
|
||||
boolean lastWasNewLine = false;
|
||||
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
String singleLine = getSingleLineOfChildren(tagChildren);
|
||||
boolean dontEscape = dontEscape(tagNode);
|
||||
if (!preserveWhitespaces && singleLine != null) {
|
||||
writer.write( !dontEscape(tagNode) ? escapeText(singleLine) : singleLine );
|
||||
} else {
|
||||
Iterator childIterator = tagChildren.iterator();
|
||||
while (childIterator.hasNext()) {
|
||||
Object child = childIterator.next();
|
||||
if (child instanceof TagNode) {
|
||||
serializePrettyHtml((TagNode)child, writer, isHeadlessNode ? level : level + 1, preserveWhitespaces, lastWasNewLine);
|
||||
lastWasNewLine = false;
|
||||
} else if (child instanceof ContentNode) {
|
||||
String content = dontEscape ? child.toString() : escapeText(child.toString());
|
||||
if (content.length() > 0) {
|
||||
if (dontEscape || preserveWhitespaces) {
|
||||
writer.write(content);
|
||||
} else if (Character.isWhitespace(content.charAt(0))) {
|
||||
if (!lastWasNewLine) {
|
||||
writer.write("\n");
|
||||
lastWasNewLine = false;
|
||||
}
|
||||
if (content.trim().length() > 0) {
|
||||
writer.write( getIndentedText(Utils.rtrim(content), isHeadlessNode ? level : level + 1) );
|
||||
} else {
|
||||
lastWasNewLine = true;
|
||||
}
|
||||
} else {
|
||||
if (content.trim().length() > 0) {
|
||||
writer.write(Utils.rtrim(content));
|
||||
}
|
||||
if (!childIterator.hasNext()) {
|
||||
writer.write("\n");
|
||||
lastWasNewLine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (child instanceof CommentNode) {
|
||||
if (!lastWasNewLine && !preserveWhitespaces) {
|
||||
writer.write("\n");
|
||||
lastWasNewLine = false;
|
||||
}
|
||||
CommentNode commentNode = (CommentNode) child;
|
||||
String content = commentNode.getCommentedContent();
|
||||
writer.write( dontEscape ? content : getIndentedText(content, isHeadlessNode ? level : level + 1) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (singleLine == null && !preserveWhitespaces) {
|
||||
if (!lastWasNewLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.write(indent);
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Pretty XML serializer - creates resulting XML with indenting lines.</p>
|
||||
*/
|
||||
public class PrettyXmlSerializer extends XmlSerializer {
|
||||
|
||||
private static final String DEFAULT_INDENTATION_STRING = "\t";
|
||||
|
||||
private String indentString = DEFAULT_INDENTATION_STRING;
|
||||
private List<String> indents = new ArrayList<String>();
|
||||
|
||||
public PrettyXmlSerializer(CleanerProperties props) {
|
||||
this(props, DEFAULT_INDENTATION_STRING);
|
||||
}
|
||||
|
||||
public PrettyXmlSerializer(CleanerProperties props, String indentString) {
|
||||
super(props);
|
||||
this.indentString = indentString;
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
serializePrettyXml(tagNode, writer, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param level
|
||||
* @return Appropriate indentation for the specified depth.
|
||||
*/
|
||||
private synchronized String getIndent(int level) {
|
||||
int size = indents.size();
|
||||
if (size <= level) {
|
||||
String prevIndent = size == 0 ? null : indents.get(size - 1);
|
||||
for (int i = size; i <= level; i++) {
|
||||
String currIndent = prevIndent == null ? "" : prevIndent + indentString;
|
||||
indents.add(currIndent);
|
||||
prevIndent = currIndent;
|
||||
}
|
||||
}
|
||||
|
||||
return indents.get(level);
|
||||
}
|
||||
|
||||
private String getIndentedText(String content, int level) {
|
||||
String indent = getIndent(level);
|
||||
StringBuilder result = new StringBuilder( content.length() );
|
||||
StringTokenizer tokenizer = new StringTokenizer(content, "\n\r");
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String line = tokenizer.nextToken().trim();
|
||||
if (!"".equals(line)) {
|
||||
result.append(indent).append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String getSingleLineOfChildren(List children) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
Iterator childrenIt = children.iterator();
|
||||
boolean isFirst = true;
|
||||
|
||||
while (childrenIt.hasNext()) {
|
||||
Object child = childrenIt.next();
|
||||
|
||||
if ( !(child instanceof ContentNode) ) {
|
||||
return null;
|
||||
} else {
|
||||
String content = child.toString();
|
||||
|
||||
// if first item trims it from left
|
||||
if (isFirst) {
|
||||
content = Utils.ltrim(content);
|
||||
}
|
||||
|
||||
// if last item trims it from right
|
||||
if (!childrenIt.hasNext()) {
|
||||
content = Utils.rtrim(content);
|
||||
}
|
||||
|
||||
if ( content.indexOf("\n") >= 0 || content.indexOf("\r") >= 0 ) {
|
||||
return null;
|
||||
}
|
||||
result.append(content);
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected void serializePrettyXml(TagNode tagNode, Writer writer, int level) throws IOException {
|
||||
List tagChildren = tagNode.getChildren();
|
||||
boolean isHeadlessNode = Utils.isEmptyString(tagNode.getName());
|
||||
String indent = isHeadlessNode ? "" : getIndent(level);
|
||||
|
||||
writer.write(indent);
|
||||
serializeOpenTag(tagNode, writer, true);
|
||||
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
String singleLine = getSingleLineOfChildren(tagChildren);
|
||||
boolean dontEscape = dontEscape(tagNode);
|
||||
if (singleLine != null) {
|
||||
if ( !dontEscape(tagNode) ) {
|
||||
writer.write( escapeXml(singleLine) );
|
||||
} else {
|
||||
writer.write( singleLine.replaceAll("]]>", "]]>") );
|
||||
}
|
||||
} else {
|
||||
if (!isHeadlessNode) {
|
||||
writer.write("\n");
|
||||
}
|
||||
for (Object child: tagChildren) {
|
||||
if (child instanceof TagNode) {
|
||||
serializePrettyXml( (TagNode)child, writer, isHeadlessNode ? level : level + 1 );
|
||||
} else if (child instanceof ContentNode) {
|
||||
String content = dontEscape ? child.toString().replaceAll("]]>", "]]>") : escapeXml(child.toString());
|
||||
writer.write( getIndentedText(content, isHeadlessNode ? level : level + 1) );
|
||||
} else if (child instanceof CommentNode) {
|
||||
CommentNode commentNode = (CommentNode) child;
|
||||
String content = commentNode.getCommentedContent();
|
||||
writer.write( getIndentedText(content, isHeadlessNode ? level : level + 1) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (singleLine == null) {
|
||||
writer.write(indent);
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Basic abstract serializer - contains common logic for descendants (methods <code>writeXXX()</code>.</p>
|
||||
*/
|
||||
public abstract class Serializer {
|
||||
|
||||
/**
|
||||
* Used to implement serialization with missing envelope - omiting open and close tags, just
|
||||
* serialize children.
|
||||
*/
|
||||
private class HeadlessTagNode extends TagNode {
|
||||
private HeadlessTagNode(TagNode wrappedNode) {
|
||||
super("");
|
||||
getAttributes().putAll(wrappedNode.getAttributes());
|
||||
getChildren().addAll(wrappedNode.getChildren());
|
||||
setDocType(wrappedNode.getDocType());
|
||||
Map<String, String> nsDecls = getNamespaceDeclarations();
|
||||
if (nsDecls != null) {
|
||||
Map<String, String> wrappedNSDecls = wrappedNode.getNamespaceDeclarations();
|
||||
if (wrappedNSDecls != null) {
|
||||
nsDecls.putAll(wrappedNSDecls);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected CleanerProperties props;
|
||||
|
||||
protected Serializer(CleanerProperties props) {
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using specified charset and optionally omits node envelope
|
||||
* (skips open and close tags of the node).
|
||||
* @param tagNode Node to be written
|
||||
* @param out Output stream
|
||||
* @param charset Charset of the output
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(TagNode tagNode, OutputStream out, String charset, boolean omitEnvelope) throws IOException {
|
||||
write( tagNode, new OutputStreamWriter(out, charset), charset, omitEnvelope );
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using specified charset.
|
||||
* @param tagNode Node to be written
|
||||
* @param out Output stream
|
||||
* @param charset Charset of the output
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(TagNode tagNode, OutputStream out, String charset) throws IOException {
|
||||
writeToStream(tagNode, out, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using system default charset and optionally omits node envelope
|
||||
* (skips open and close tags of the node).
|
||||
* @param tagNode Node to be written
|
||||
* @param out Output stream
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(TagNode tagNode, OutputStream out, boolean omitEnvelope) throws IOException {
|
||||
writeToStream( tagNode, out, HtmlCleaner.DEFAULT_CHARSET, omitEnvelope );
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using system default charset.
|
||||
* @param tagNode Node to be written
|
||||
* @param out Output stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(TagNode tagNode, OutputStream out) throws IOException {
|
||||
writeToStream(tagNode, out, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using specified charset and optionally omits node envelope
|
||||
* (skips open and close tags of the node).
|
||||
* @param tagNode Node to be written
|
||||
* @param fileName Output file name
|
||||
* @param charset Charset of the output
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(TagNode tagNode, String fileName, String charset, boolean omitEnvelope) throws IOException {
|
||||
writeToStream(tagNode, new FileOutputStream(fileName), charset, omitEnvelope );
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using specified charset.
|
||||
* @param tagNode Node to be written
|
||||
* @param fileName Output file name
|
||||
* @param charset Charset of the output
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(TagNode tagNode, String fileName, String charset) throws IOException {
|
||||
writeToFile(tagNode, fileName, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using specified charset and optionally omits node envelope
|
||||
* (skips open and close tags of the node).
|
||||
* @param tagNode Node to be written
|
||||
* @param fileName Output file name
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(TagNode tagNode, String fileName, boolean omitEnvelope) throws IOException {
|
||||
writeToFile(tagNode,fileName, HtmlCleaner.DEFAULT_CHARSET, omitEnvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using system default charset.
|
||||
* @param tagNode Node to be written
|
||||
* @param fileName Output file name
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(TagNode tagNode, String fileName) throws IOException {
|
||||
writeToFile(tagNode, fileName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode Node to serialize to string
|
||||
* @param charset Charset of the output - stands in xml declaration part
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(TagNode tagNode, String charset, boolean omitEnvelope) throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
write(tagNode, writer, charset, omitEnvelope);
|
||||
return writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode Node to serialize to string
|
||||
* @param charset Charset of the output - stands in xml declaration part
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(TagNode tagNode, String charset) throws IOException {
|
||||
return getAsString(tagNode, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode Node to serialize to string
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(TagNode tagNode, boolean omitEnvelope) throws IOException {
|
||||
return getAsString(tagNode, HtmlCleaner.DEFAULT_CHARSET, omitEnvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode Node to serialize to string
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(TagNode tagNode) throws IOException {
|
||||
return getAsString(tagNode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified node using specified writer.
|
||||
* @param tagNode Node to serialize.
|
||||
* @param writer Writer instance
|
||||
* @param charset Charset of the output
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(TagNode tagNode, Writer writer, String charset) throws IOException {
|
||||
write(tagNode, writer, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified node using specified writer.
|
||||
* @param tagNode Node to serialize.
|
||||
* @param writer Writer instance
|
||||
* @param charset Charset of the output
|
||||
* @param omitEnvelope Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(TagNode tagNode, Writer writer, String charset, boolean omitEnvelope) throws IOException {
|
||||
if (omitEnvelope) {
|
||||
tagNode = new HeadlessTagNode(tagNode);
|
||||
}
|
||||
writer = new BufferedWriter(writer);
|
||||
if ( !props.isOmitXmlDeclaration() ) {
|
||||
String declaration = "<?xml version=\"1.0\"";
|
||||
if (charset != null) {
|
||||
declaration += " encoding=\"" + charset + "\"";
|
||||
}
|
||||
declaration += "?>";
|
||||
writer.write(declaration + "\n");
|
||||
}
|
||||
|
||||
if ( !props.isOmitDoctypeDeclaration() ) {
|
||||
DoctypeToken doctypeToken = tagNode.getDocType();
|
||||
if ( doctypeToken != null ) {
|
||||
doctypeToken.serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serialize(tagNode, writer);
|
||||
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
protected boolean isScriptOrStyle(TagNode tagNode) {
|
||||
String tagName = tagNode.getName();
|
||||
return "script".equalsIgnoreCase(tagName) || "style".equalsIgnoreCase(tagName);
|
||||
}
|
||||
|
||||
protected abstract void serialize(TagNode tagNode, Writer writer) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* <p>Simple HTML serializer - creates resulting HTML without indenting and/or compacting.</p>
|
||||
*/
|
||||
public class SimpleHtmlSerializer extends HtmlSerializer {
|
||||
|
||||
public SimpleHtmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
for (Object item: tagNode.getChildren()) {
|
||||
if ( item instanceof ContentNode) {
|
||||
String content = item.toString();
|
||||
writer.write( dontEscape(tagNode) ? content : escapeText(content) );
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken)item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>Simple XML serializer - creates resulting XML without indenting lines.</p>
|
||||
*/
|
||||
public class SimpleXmlSerializer extends XmlSerializer {
|
||||
|
||||
public SimpleXmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(TagNode tagNode, Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
if ( !isMinimizedTagSyntax(tagNode) ) {
|
||||
for (Object item: tagNode.getChildren()) {
|
||||
if ( item instanceof ContentNode) {
|
||||
String content = item.toString();
|
||||
writer.write( dontEscape(tagNode) ? content.replaceAll("]]>", "]]>") : escapeXml(content) );
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken)item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>This class contains map with special entities used in HTML and their unicodes.</p>
|
||||
*/
|
||||
public class SpecialEntity {
|
||||
|
||||
private static Map<String, SpecialEntity> entities = new HashMap<String, SpecialEntity>();
|
||||
|
||||
private static int maxEntityLength = 0;
|
||||
|
||||
/**
|
||||
* Add new entity to the set.
|
||||
* @param entityName Entity name, for example "pound"
|
||||
* @param intCode Unicode of the entity, for example 163
|
||||
*
|
||||
* @throws org.htmlcleaner.HtmlCleanerException
|
||||
*/
|
||||
public static void addEntity(String entityName, int intCode) throws HtmlCleanerException {
|
||||
if (entities.containsKey(entityName)) {
|
||||
throw new HtmlCleanerException("Entity \"" + entityName + "\" is already defined!");
|
||||
}
|
||||
entities.put(entityName, new SpecialEntity(entityName, intCode));
|
||||
int entityNameLen = entityName.length();
|
||||
if (entityNameLen > maxEntityLength) {
|
||||
maxEntityLength = entityNameLen;
|
||||
};
|
||||
}
|
||||
|
||||
public static SpecialEntity getEntity(String key) {
|
||||
return entities.get(key);
|
||||
}
|
||||
|
||||
static int getMaxEntityLength() {
|
||||
return maxEntityLength;
|
||||
}
|
||||
|
||||
static {
|
||||
addEntity("nbsp", 160);
|
||||
addEntity("iexcl", 161);
|
||||
addEntity("cent", 162);
|
||||
addEntity("pound", 163);
|
||||
addEntity("curren", 164);
|
||||
addEntity("yen", 165);
|
||||
addEntity("brvbar", 166);
|
||||
addEntity("sect", 167);
|
||||
addEntity("uml", 168);
|
||||
addEntity("copy", 169);
|
||||
addEntity("ordf", 170);
|
||||
addEntity("laquo", 171);
|
||||
addEntity("not", 172);
|
||||
addEntity("shy", 173);
|
||||
addEntity("reg", 174);
|
||||
addEntity("macr", 175);
|
||||
addEntity("deg", 176);
|
||||
addEntity("plusmn", 177);
|
||||
addEntity("sup2", 178);
|
||||
addEntity("sup3", 179);
|
||||
addEntity("acute", 180);
|
||||
addEntity("micro", 181);
|
||||
addEntity("para", 182);
|
||||
addEntity("middot", 183);
|
||||
addEntity("cedil", 184);
|
||||
addEntity("sup1", 185);
|
||||
addEntity("ordm", 186);
|
||||
addEntity("raquo", 187);
|
||||
addEntity("frac14", 188);
|
||||
addEntity("frac12", 189);
|
||||
addEntity("frac34", 190);
|
||||
addEntity("iquest", 191);
|
||||
addEntity("Agrave", 192);
|
||||
addEntity("Aacute", 193);
|
||||
addEntity("Acirc", 194);
|
||||
addEntity("Atilde", 195);
|
||||
addEntity("Auml", 196);
|
||||
addEntity("Aring", 197);
|
||||
addEntity("AElig", 198);
|
||||
addEntity("Ccedil", 199);
|
||||
addEntity("Egrave", 200);
|
||||
addEntity("Eacute", 201);
|
||||
addEntity("Ecirc", 202);
|
||||
addEntity("Euml", 203);
|
||||
addEntity("Igrave", 204);
|
||||
addEntity("Iacute", 205);
|
||||
addEntity("Icirc", 206);
|
||||
addEntity("Iuml", 207);
|
||||
addEntity("ETH", 208);
|
||||
addEntity("Ntilde", 209);
|
||||
addEntity("Ograve", 210);
|
||||
addEntity("Oacute", 211);
|
||||
addEntity("Ocirc", 212);
|
||||
addEntity("Otilde", 213);
|
||||
addEntity("Ouml", 214);
|
||||
addEntity("times", 215);
|
||||
addEntity("Oslash", 216);
|
||||
addEntity("Ugrave", 217);
|
||||
addEntity("Uacute", 218);
|
||||
addEntity("Ucirc", 219);
|
||||
addEntity("Uuml", 220);
|
||||
addEntity("Yacute", 221);
|
||||
addEntity("THORN", 222);
|
||||
addEntity("szlig", 223);
|
||||
addEntity("agrave", 224);
|
||||
addEntity("aacute", 225);
|
||||
addEntity("acirc", 226);
|
||||
addEntity("atilde", 227);
|
||||
addEntity("auml", 228);
|
||||
addEntity("aring", 229);
|
||||
addEntity("aelig", 230);
|
||||
addEntity("ccedil", 231);
|
||||
addEntity("egrave", 232);
|
||||
addEntity("eacute", 233);
|
||||
addEntity("ecirc", 234);
|
||||
addEntity("euml", 235);
|
||||
addEntity("igrave", 236);
|
||||
addEntity("iacute", 237);
|
||||
addEntity("icirc", 238);
|
||||
addEntity("iuml", 239);
|
||||
addEntity("eth", 240);
|
||||
addEntity("ntilde", 241);
|
||||
addEntity("ograve", 242);
|
||||
addEntity("oacute", 243);
|
||||
addEntity("ocirc", 244);
|
||||
addEntity("otilde", 245);
|
||||
addEntity("ouml", 246);
|
||||
addEntity("divide", 247);
|
||||
addEntity("oslash", 248);
|
||||
addEntity("ugrave", 249);
|
||||
addEntity("uacute", 250);
|
||||
addEntity("ucirc", 251);
|
||||
addEntity("uuml", 252);
|
||||
addEntity("yacute", 253);
|
||||
addEntity("thorn", 254);
|
||||
addEntity("yuml", 255);
|
||||
addEntity("OElig", 338);
|
||||
addEntity("oelig", 339);
|
||||
addEntity("Scaron", 352);
|
||||
addEntity("scaron", 353);
|
||||
addEntity("Yuml", 376);
|
||||
addEntity("fnof", 402);
|
||||
addEntity("circ", 710);
|
||||
addEntity("tilde", 732);
|
||||
|
||||
// Greek letters
|
||||
addEntity("Alpha", 913);
|
||||
addEntity("Beta", 914);
|
||||
addEntity("Gamma", 915);
|
||||
addEntity("Delta", 916);
|
||||
addEntity("Epsilon", 917);
|
||||
addEntity("Zeta", 918);
|
||||
addEntity("Eta", 919);
|
||||
addEntity("Theta", 920);
|
||||
addEntity("Iota", 921);
|
||||
addEntity("Kappa", 922);
|
||||
addEntity("Lambda", 923);
|
||||
addEntity("Mu", 924);
|
||||
addEntity("Nu", 925);
|
||||
addEntity("Xi", 926);
|
||||
addEntity("Omicron", 927);
|
||||
addEntity("Pi", 928);
|
||||
addEntity("Rho", 929);
|
||||
addEntity("Sigma", 931);
|
||||
addEntity("Tau", 932);
|
||||
addEntity("Upsilon", 933);
|
||||
addEntity("Phi", 934);
|
||||
addEntity("Chi", 935);
|
||||
addEntity("Psi", 936);
|
||||
addEntity("Omega", 937);
|
||||
addEntity("alpha", 945);
|
||||
addEntity("beta", 946);
|
||||
addEntity("gamma", 947);
|
||||
addEntity("delta", 948);
|
||||
addEntity("epsilon", 949);
|
||||
addEntity("zeta", 950);
|
||||
addEntity("eta", 951);
|
||||
addEntity("theta", 952);
|
||||
addEntity("iota", 953);
|
||||
addEntity("kappa", 954);
|
||||
addEntity("lambda", 955);
|
||||
addEntity("mu", 956);
|
||||
addEntity("nu", 957);
|
||||
addEntity("xi", 958);
|
||||
addEntity("omicron", 959);
|
||||
addEntity("pi", 960);
|
||||
addEntity("rho", 961);
|
||||
addEntity("sigmaf", 962);
|
||||
addEntity("sigma", 963);
|
||||
addEntity("tau", 964);
|
||||
addEntity("upsilon", 965);
|
||||
addEntity("phi", 966);
|
||||
addEntity("chi", 967);
|
||||
addEntity("psi", 968);
|
||||
addEntity("omega", 969);
|
||||
addEntity("thetasym", 977);
|
||||
addEntity("upsih", 978);
|
||||
addEntity("piv", 982);
|
||||
|
||||
addEntity("ensp", 8194);
|
||||
addEntity("emsp", 8195);
|
||||
addEntity("thinsp", 8201);
|
||||
addEntity("zwnj", 8204);
|
||||
addEntity("zwj", 8205);
|
||||
addEntity("lrm", 8206);
|
||||
addEntity("rlm", 8207);
|
||||
addEntity("ndash", 8211);
|
||||
addEntity("mdash", 8212);
|
||||
addEntity("lsquo", 8216);
|
||||
addEntity("rsquo", 8217);
|
||||
addEntity("sbquo", 8218);
|
||||
addEntity("ldquo", 8220);
|
||||
addEntity("rdquo", 8221);
|
||||
addEntity("bdquo", 8222);
|
||||
addEntity("dagger", 8224);
|
||||
addEntity("Dagger", 8225);
|
||||
addEntity("bull", 8226);
|
||||
|
||||
addEntity("hellip", 8230);
|
||||
addEntity("permil", 8240);
|
||||
addEntity("prime", 8242);
|
||||
addEntity("Prime", 8243);
|
||||
addEntity("lsaquo", 8249);
|
||||
addEntity("rsaquo", 8250);
|
||||
addEntity("oline", 8254);
|
||||
addEntity("frasl", 8260);
|
||||
addEntity("euro", 8364);
|
||||
addEntity("image", 8465);
|
||||
addEntity("weierp", 8472);
|
||||
addEntity("real", 8476);
|
||||
addEntity("trade", 8482);
|
||||
addEntity("alefsym", 8501);
|
||||
addEntity("larr", 8592);
|
||||
addEntity("uarr", 8593);
|
||||
addEntity("rarr", 8594);
|
||||
addEntity("darr", 8595);
|
||||
addEntity("harr", 8596);
|
||||
addEntity("crarr", 8629);
|
||||
addEntity("lArr", 8656);
|
||||
addEntity("uArr", 8657);
|
||||
addEntity("rArr", 8658);
|
||||
addEntity("dArr", 8659);
|
||||
addEntity("hArr", 8660);
|
||||
|
||||
// math symbols
|
||||
addEntity("forall", 8704);
|
||||
addEntity("part", 8706);
|
||||
addEntity("exist", 8707);
|
||||
addEntity("empty", 8709);
|
||||
addEntity("nabla", 8711);
|
||||
addEntity("isin", 8712);
|
||||
addEntity("notin", 8713);
|
||||
addEntity("ni", 8715);
|
||||
addEntity("prod", 8719);
|
||||
addEntity("sum", 8721);
|
||||
addEntity("minus", 8722);
|
||||
addEntity("lowast", 8727);
|
||||
addEntity("radic", 8730);
|
||||
addEntity("prop", 8733);
|
||||
addEntity("infin", 8734);
|
||||
addEntity("ang", 8736);
|
||||
addEntity("and", 8743);
|
||||
addEntity("or", 8744);
|
||||
addEntity("cap", 8745);
|
||||
addEntity("cup", 8746);
|
||||
addEntity("int", 8747);
|
||||
addEntity("there4", 8756);
|
||||
addEntity("sim", 8764);
|
||||
addEntity("cong", 8773);
|
||||
addEntity("asymp", 8776);
|
||||
addEntity("ne", 8800);
|
||||
addEntity("equiv", 8801);
|
||||
addEntity("le", 8804);
|
||||
addEntity("ge", 8805);
|
||||
addEntity("sub", 8834);
|
||||
addEntity("sup", 8835);
|
||||
addEntity("nsub", 8836);
|
||||
addEntity("sube", 8838);
|
||||
addEntity("supe", 8839);
|
||||
addEntity("oplus", 8853);
|
||||
addEntity("otimes", 8855);
|
||||
addEntity("perp", 8869);
|
||||
addEntity("sdot", 8901);
|
||||
addEntity("lceil", 8968);
|
||||
addEntity("rceil", 8969);
|
||||
addEntity("lfloor", 8970);
|
||||
addEntity("rfloor", 8971);
|
||||
addEntity("lang", 9001);
|
||||
addEntity("rang", 9002);
|
||||
addEntity("loz", 9674);
|
||||
addEntity("spades", 9824);
|
||||
addEntity("clubs", 9827);
|
||||
addEntity("hearts", 9829);
|
||||
addEntity("diams", 9830);
|
||||
}
|
||||
|
||||
|
||||
private String key;
|
||||
private int intCode;
|
||||
|
||||
private SpecialEntity(String key, int intCode) {
|
||||
this.key = key;
|
||||
this.intCode = intCode;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getIntCode() {
|
||||
return intCode;
|
||||
}
|
||||
|
||||
public char getCharacter() {
|
||||
return (char)intCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Numeric Character Reference in decimal format
|
||||
*/
|
||||
public String getDecimalNCR() {
|
||||
return "&#" + intCode + ";";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Numeric Character Reference in hex format
|
||||
*/
|
||||
public String getHexNCR() {
|
||||
return "&#x" + Integer.toHexString(intCode) + ";";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Escaped value of the entity
|
||||
*/
|
||||
public String getEscapedValue() {
|
||||
return "&" + key + ";";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class contains information about single HTML tag.<br/>
|
||||
* It also contains rules for tag balancing. For each tag, list of dependant
|
||||
* tags may be defined. There are several kinds of dependancies used to reorder
|
||||
* tags:
|
||||
* <ul>
|
||||
* <li>
|
||||
* fatal tags - required outer tag - the tag will be ignored during
|
||||
* parsing (will be skipped) if this fatal tag is missing. For example, most web
|
||||
* browsers ignore elements TD, TR, TBODY if they are not in the context of TABLE tag.
|
||||
* </li>
|
||||
* <li>
|
||||
* required enclosing tags - if there is no such, it is implicitely
|
||||
* created. For example if TD is out of TR - open TR is created before.
|
||||
* </li>
|
||||
* <li>
|
||||
* forbidden tags - it is not allowed to occure inside - for example
|
||||
* FORM cannot be inside other FORM and it will be ignored during cleanup.
|
||||
* </li>
|
||||
* <li>
|
||||
* allowed children tags - for example TR allowes TD and TH. If there
|
||||
* are some dependant allowed tags defined then cleaner ignores other tags, treating
|
||||
* them as unallowed, unless they are in some other relationship with this tag.
|
||||
* </li>
|
||||
* <li>
|
||||
* higher level tags - for example for TR higher tags are THEAD, TBODY, TFOOT.
|
||||
* </li>
|
||||
* <li>
|
||||
* tags that must be closed and copied - for example, in
|
||||
* <code><a href="#"><div>....</code> tag A must be closed before DIV but
|
||||
* copied again inside DIV.
|
||||
* </li>
|
||||
* <li>
|
||||
* tags that must be closed before closing this tag and copied again after -
|
||||
* for example, in <code><i><b>at</i> first</b> text </code>
|
||||
* tag B must be closed before closing I, but it must be copied again after resulting
|
||||
* finally in sequence: <code><i><b>at</b></i><b> first</b> text </code>.
|
||||
* </li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Tag TR for instance (table row) may define the following dependancies:
|
||||
* <ul>
|
||||
* <li>fatal tag is <code>table</code></li>
|
||||
* <li>required enclosing tag is <code>tbody</code></li>
|
||||
* <li>allowed children tags are <code>td,th</code></li>
|
||||
* <li>higher level tags are <code>thead,tfoot</code></li>
|
||||
* <li>tags that muste be closed before are <code>tr,td,th,caption,colgroup</code></li>
|
||||
* </ul>
|
||||
* meaning the following: <br>
|
||||
* <ul>
|
||||
* <li><code>tr</code> must be in context of <code>table</code>, otherwise it will be ignored,</li>
|
||||
* <li><code>tr</code> may can be directly inside <code>tbody</code>, <code>tfoot</code> and <code>thead</code>,
|
||||
* otherwise <code>tbody</code> will be implicitely created in front of it.</li>
|
||||
* <li><code>tr</code> can contain <code>td</code> and <code>th</code>, all other tags and content will be pushed out of current
|
||||
* limiting context, in the case of html tables, in front of enclosing <code>table</code> tag.</li>
|
||||
* <li>if previous open tag is one of <code>tr</code>, <code>caption</code> or <code>colgroup</code>, it will be implicitely closed.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public class TagInfo {
|
||||
|
||||
protected static final int HEAD_AND_BODY = 0;
|
||||
protected static final int HEAD = 1;
|
||||
protected static final int BODY = 2;
|
||||
|
||||
protected static final int CONTENT_ALL = 0;
|
||||
protected static final int CONTENT_NONE = 1;
|
||||
protected static final int CONTENT_TEXT = 2;
|
||||
|
||||
private String name;
|
||||
private int contentType;
|
||||
private Set mustCloseTags = new HashSet();
|
||||
private Set higherTags = new HashSet();
|
||||
private Set childTags = new HashSet();
|
||||
private Set permittedTags = new HashSet();
|
||||
private Set copyTags = new HashSet();
|
||||
private Set continueAfterTags = new HashSet();
|
||||
private int belongsTo = BODY;
|
||||
private String requiredParent = null;
|
||||
private String fatalTag = null;
|
||||
private boolean deprecated = false;
|
||||
private boolean unique = false;
|
||||
private boolean ignorePermitted = false;
|
||||
|
||||
|
||||
public TagInfo(String name, int contentType, int belongsTo, boolean depricated, boolean unique, boolean ignorePermitted) {
|
||||
this.name = name;
|
||||
this.contentType = contentType;
|
||||
this.belongsTo = belongsTo;
|
||||
this.deprecated = depricated;
|
||||
this.unique = unique;
|
||||
this.ignorePermitted = ignorePermitted;
|
||||
}
|
||||
|
||||
public void defineFatalTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.fatalTag = currTag;
|
||||
this.higherTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineRequiredEnclosingTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.requiredParent = currTag;
|
||||
this.higherTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineForbiddenTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.permittedTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineAllowedChildrenTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.childTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineHigherLevelTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.higherTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineCloseBeforeCopyInsideTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.copyTags.add(currTag);
|
||||
this.mustCloseTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineCloseInsideCopyAfterTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.continueAfterTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineCloseBeforeTags(String commaSeparatedListOfTags) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String currTag = tokenizer.nextToken();
|
||||
this.mustCloseTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public Set getMustCloseTags() {
|
||||
return mustCloseTags;
|
||||
}
|
||||
|
||||
public void setMustCloseTags(Set mustCloseTags) {
|
||||
this.mustCloseTags = mustCloseTags;
|
||||
}
|
||||
|
||||
public Set getHigherTags() {
|
||||
return higherTags;
|
||||
}
|
||||
|
||||
public void setHigherTags(Set higherTags) {
|
||||
this.higherTags = higherTags;
|
||||
}
|
||||
|
||||
public Set getChildTags() {
|
||||
return childTags;
|
||||
}
|
||||
|
||||
public void setChildTags(Set childTags) {
|
||||
this.childTags = childTags;
|
||||
}
|
||||
|
||||
public Set getPermittedTags() {
|
||||
return permittedTags;
|
||||
}
|
||||
|
||||
public void setPermittedTags(Set permittedTags) {
|
||||
this.permittedTags = permittedTags;
|
||||
}
|
||||
|
||||
public Set getCopyTags() {
|
||||
return copyTags;
|
||||
}
|
||||
|
||||
public void setCopyTags(Set copyTags) {
|
||||
this.copyTags = copyTags;
|
||||
}
|
||||
|
||||
public Set getContinueAfterTags() {
|
||||
return continueAfterTags;
|
||||
}
|
||||
|
||||
public void setContinueAfterTags(Set continueAfterTags) {
|
||||
this.continueAfterTags = continueAfterTags;
|
||||
}
|
||||
|
||||
public String getRequiredParent() {
|
||||
return requiredParent;
|
||||
}
|
||||
|
||||
public void setRequiredParent(String requiredParent) {
|
||||
this.requiredParent = requiredParent;
|
||||
}
|
||||
|
||||
public int getBelongsTo() {
|
||||
return belongsTo;
|
||||
}
|
||||
|
||||
public void setBelongsTo(int belongsTo) {
|
||||
this.belongsTo = belongsTo;
|
||||
}
|
||||
|
||||
public String getFatalTag() {
|
||||
return fatalTag;
|
||||
}
|
||||
|
||||
public void setFatalTag(String fatalTag) {
|
||||
this.fatalTag = fatalTag;
|
||||
}
|
||||
|
||||
public boolean isDeprecated() {
|
||||
return deprecated;
|
||||
}
|
||||
|
||||
public void setDeprecated(boolean deprecated) {
|
||||
this.deprecated = deprecated;
|
||||
}
|
||||
|
||||
public boolean isUnique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
public void setUnique(boolean unique) {
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
public boolean isIgnorePermitted() {
|
||||
return ignorePermitted;
|
||||
}
|
||||
|
||||
public boolean isEmptyTag() {
|
||||
return CONTENT_NONE == contentType;
|
||||
}
|
||||
|
||||
public void setIgnorePermitted(boolean ignorePermitted) {
|
||||
this.ignorePermitted = ignorePermitted;
|
||||
}
|
||||
|
||||
// other functionality
|
||||
|
||||
boolean allowsBody() {
|
||||
return CONTENT_NONE != contentType;
|
||||
}
|
||||
|
||||
boolean isHigher(String tagName) {
|
||||
return higherTags.contains(tagName);
|
||||
}
|
||||
|
||||
boolean isCopy(String tagName) {
|
||||
return copyTags.contains(tagName);
|
||||
}
|
||||
|
||||
boolean hasCopyTags() {
|
||||
return !copyTags.isEmpty();
|
||||
}
|
||||
|
||||
boolean isContinueAfter(String tagName) {
|
||||
return continueAfterTags.contains(tagName);
|
||||
}
|
||||
|
||||
boolean hasPermittedTags() {
|
||||
return !permittedTags.isEmpty();
|
||||
}
|
||||
|
||||
boolean isHeadTag() {
|
||||
return belongsTo == HEAD;
|
||||
}
|
||||
|
||||
boolean isHeadAndBodyTag() {
|
||||
return belongsTo == HEAD || belongsTo == HEAD_AND_BODY;
|
||||
}
|
||||
|
||||
boolean isMustCloseTag(TagInfo tagInfo) {
|
||||
if (tagInfo != null) {
|
||||
return mustCloseTags.contains( tagInfo.getName() ) || tagInfo.contentType == CONTENT_TEXT;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean allowsItem(BaseToken token) {
|
||||
if ( contentType != CONTENT_NONE && token instanceof TagToken ) {
|
||||
TagToken tagToken = (TagToken) token;
|
||||
String tagName = tagToken.getName();
|
||||
if ( "script".equals(tagName) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CONTENT_ALL == contentType) {
|
||||
if ( !childTags.isEmpty() ) {
|
||||
return token instanceof TagToken ? childTags.contains( ((TagToken)token).getName() ) : false;
|
||||
} else if ( !permittedTags.isEmpty() ) {
|
||||
return token instanceof TagToken ? !permittedTags.contains( ((TagToken)token).getName() ) : true;
|
||||
}
|
||||
return true;
|
||||
} else if ( CONTENT_TEXT == contentType ) {
|
||||
return !(token instanceof TagToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean allowsAnything() {
|
||||
return CONTENT_ALL == contentType && childTags.size() == 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,714 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XML node tag - basic node of the cleaned HTML tree. At the same time, it represents start tag token
|
||||
* after HTML parsing phase and before cleaning phase. After cleaning process, tree structure remains
|
||||
* containing tag nodes (TagNode class), content (text nodes - ContentNode), comments (CommentNode)
|
||||
* and optionally doctype node (DoctypeToken).
|
||||
* </p>
|
||||
*/
|
||||
public class TagNode extends TagToken implements HtmlNode {
|
||||
|
||||
/**
|
||||
* Used as base for different node checkers.
|
||||
*/
|
||||
public interface ITagNodeCondition {
|
||||
public boolean satisfy(TagNode tagNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* All nodes.
|
||||
*/
|
||||
public class TagAllCondition implements ITagNodeCondition {
|
||||
public boolean satisfy(TagNode tagNode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node has specified name.
|
||||
*/
|
||||
public class TagNodeNameCondition implements ITagNodeCondition {
|
||||
private String name;
|
||||
|
||||
public TagNodeNameCondition(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean satisfy(TagNode tagNode) {
|
||||
return tagNode == null ? false : tagNode.name.equalsIgnoreCase(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node contains specified attribute.
|
||||
*/
|
||||
public class TagNodeAttExistsCondition implements ITagNodeCondition {
|
||||
private String attName;
|
||||
|
||||
public TagNodeAttExistsCondition(String attName) {
|
||||
this.attName = attName;
|
||||
}
|
||||
|
||||
public boolean satisfy(TagNode tagNode) {
|
||||
return tagNode == null ? false : tagNode.attributes.containsKey( attName.toLowerCase() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node has specified attribute with specified value.
|
||||
*/
|
||||
public class TagNodeAttValueCondition implements ITagNodeCondition {
|
||||
private String attName;
|
||||
private String attValue;
|
||||
private boolean isCaseSensitive;
|
||||
|
||||
public TagNodeAttValueCondition(String attName, String attValue, boolean isCaseSensitive) {
|
||||
this.attName = attName;
|
||||
this.attValue = attValue;
|
||||
this.isCaseSensitive = isCaseSensitive;
|
||||
}
|
||||
|
||||
public boolean satisfy(TagNode tagNode) {
|
||||
if (tagNode == null || attName == null || attValue == null) {
|
||||
return false;
|
||||
} else {
|
||||
return isCaseSensitive ?
|
||||
attValue.equals( tagNode.getAttributeByName(attName) ) :
|
||||
attValue.equalsIgnoreCase( tagNode.getAttributeByName(attName) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TagNode parent = null;
|
||||
private Map<String, String> attributes = new LinkedHashMap<String, String>();
|
||||
private List children = new ArrayList();
|
||||
private DoctypeToken docType = null;
|
||||
private Map<String, String> nsDeclarations = null;
|
||||
private List<BaseToken> itemsToMove = null;
|
||||
|
||||
private transient boolean isFormed = false;
|
||||
|
||||
|
||||
public TagNode(String name) {
|
||||
super(name == null ? null : name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes name of the tag
|
||||
* @param name
|
||||
* @return True if new name is valid, false otherwise
|
||||
*/
|
||||
public boolean setName(String name) {
|
||||
if (Utils.isValidXmlIdentifier(name)) {
|
||||
this.name = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attName
|
||||
* @return Value of the specified attribute, or null if it this tag doesn't contain it.
|
||||
*/
|
||||
public String getAttributeByName(String attName) {
|
||||
return attName != null ? attributes.get(attName.toLowerCase()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map instance containing all attribute name/value pairs.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks existance of specified attribute.
|
||||
* @param attName
|
||||
*/
|
||||
public boolean hasAttribute(String attName) {
|
||||
return attName != null ? attributes.containsKey(attName.toLowerCase()) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use setAttribute instead
|
||||
* Adds specified attribute to this tag or overrides existing one.
|
||||
* @param attName
|
||||
* @param attValue
|
||||
*/
|
||||
@Deprecated
|
||||
public void addAttribute(String attName, String attValue) {
|
||||
setAttribute(attName, attValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding new attribute ir overriding existing one.
|
||||
* @param attName
|
||||
* @param attValue
|
||||
*/
|
||||
public void setAttribute(String attName, String attValue) {
|
||||
if ( attName != null && !"".equals(attName.trim()) ) {
|
||||
attName = attName.toLowerCase();
|
||||
if ("xmlns".equals(attName)) {
|
||||
addNamespaceDeclaration("", attValue);
|
||||
} else if (attName.startsWith("xmlns:")) {
|
||||
addNamespaceDeclaration( attName.substring(6), attValue );
|
||||
} else {
|
||||
attributes.put(attName, attValue == null ? "" : attValue );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds namespace declaration to the node
|
||||
* @param nsPrefix Namespace prefix
|
||||
* @param nsURI Namespace URI
|
||||
*/
|
||||
public void addNamespaceDeclaration(String nsPrefix, String nsURI) {
|
||||
if (nsDeclarations == null) {
|
||||
nsDeclarations = new TreeMap<String, String>();
|
||||
}
|
||||
nsDeclarations.put(nsPrefix, nsURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map of namespace declarations for this node
|
||||
*/
|
||||
public Map<String, String> getNamespaceDeclarations() {
|
||||
return nsDeclarations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified attribute from this tag.
|
||||
* @param attName
|
||||
*/
|
||||
public void removeAttribute(String attName) {
|
||||
if ( attName != null && !"".equals(attName.trim()) ) {
|
||||
attributes.remove( attName.toLowerCase() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of children objects. During the cleanup process there could be different kind of
|
||||
* childern inside, however after clean there should be only TagNode instances.
|
||||
*/
|
||||
public List getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether this node has child elements or not.
|
||||
*/
|
||||
public boolean hasChildren() {
|
||||
return children.size() > 0;
|
||||
}
|
||||
|
||||
void setChildren(List children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public List getChildTagList() {
|
||||
List childTagList = new ArrayList();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
Object item = children.get(i);
|
||||
if (item instanceof TagNode) {
|
||||
childTagList.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return childTagList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An array of child TagNode instances.
|
||||
*/
|
||||
public TagNode[] getChildTags() {
|
||||
List childTagList = getChildTagList();
|
||||
TagNode childrenArray[] = new TagNode[childTagList.size()];
|
||||
for (int i = 0; i < childTagList.size(); i++) {
|
||||
childrenArray[i] = (TagNode) childTagList.get(i);
|
||||
}
|
||||
|
||||
return childrenArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Text content of this node and it's subelements.
|
||||
*/
|
||||
public StringBuffer getText() {
|
||||
StringBuffer text = new StringBuffer();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
Object item = children.get(i);
|
||||
if (item instanceof ContentNode) {
|
||||
text.append(item.toString());
|
||||
} else if (item instanceof TagNode) {
|
||||
StringBuffer subtext = ((TagNode)item).getText();
|
||||
text.append(subtext);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Parent of this node, or null if this is the root node.
|
||||
*/
|
||||
public TagNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public DoctypeToken getDocType() {
|
||||
return docType;
|
||||
}
|
||||
|
||||
public void setDocType(DoctypeToken docType) {
|
||||
this.docType = docType;
|
||||
}
|
||||
|
||||
public void addChild(Object child) {
|
||||
if (child == null) {
|
||||
return;
|
||||
}
|
||||
if (child instanceof List) {
|
||||
addChildren( (List)child );
|
||||
} else {
|
||||
children.add(child);
|
||||
if (child instanceof TagNode) {
|
||||
TagNode childTagNode = (TagNode)child;
|
||||
childTagNode.parent = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all elements from specified list to this node.
|
||||
* @param newChildren
|
||||
*/
|
||||
public void addChildren(List newChildren) {
|
||||
if (newChildren != null) {
|
||||
Iterator it = newChildren.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object child = it.next();
|
||||
addChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first element in the tree that satisfy specified condition.
|
||||
* @param condition
|
||||
* @param isRecursive
|
||||
* @return First TagNode found, or null if no such elements.
|
||||
*/
|
||||
private TagNode findElement(ITagNodeCondition condition, boolean isRecursive) {
|
||||
if (condition == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
Object item = children.get(i);
|
||||
if (item instanceof TagNode) {
|
||||
TagNode currNode = (TagNode) item;
|
||||
if ( condition.satisfy(currNode) ) {
|
||||
return currNode;
|
||||
} else if (isRecursive) {
|
||||
TagNode inner = currNode.findElement(condition, isRecursive);
|
||||
if (inner != null) {
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in the tree that satisfy specified condition.
|
||||
* @param condition
|
||||
* @param isRecursive
|
||||
* @return List of TagNode instances with specified name.
|
||||
*/
|
||||
private List getElementList(ITagNodeCondition condition, boolean isRecursive) {
|
||||
List result = new LinkedList();
|
||||
if (condition == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
Object item = children.get(i);
|
||||
if (item instanceof TagNode) {
|
||||
TagNode currNode = (TagNode) item;
|
||||
if ( condition.satisfy(currNode) ) {
|
||||
result.add(currNode);
|
||||
}
|
||||
if (isRecursive) {
|
||||
List innerList = currNode.getElementList(condition, isRecursive);
|
||||
if (innerList != null && innerList.size() > 0) {
|
||||
result.addAll(innerList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param condition
|
||||
* @param isRecursive
|
||||
* @return The array of all subelemets that satisfy specified condition.
|
||||
*/
|
||||
private TagNode[] getElements(ITagNodeCondition condition, boolean isRecursive) {
|
||||
final List list = getElementList(condition, isRecursive);
|
||||
TagNode array[] = new TagNode[ list == null ? 0 : list.size() ];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = (TagNode) list.get(i);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
public List getAllElementsList(boolean isRecursive) {
|
||||
return getElementList( new TagAllCondition(), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode[] getAllElements(boolean isRecursive) {
|
||||
return getElements( new TagAllCondition(), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode findElementByName(String findName, boolean isRecursive) {
|
||||
return findElement( new TagNodeNameCondition(findName), isRecursive );
|
||||
}
|
||||
|
||||
public List getElementListByName(String findName, boolean isRecursive) {
|
||||
return getElementList( new TagNodeNameCondition(findName), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode[] getElementsByName(String findName, boolean isRecursive) {
|
||||
return getElements( new TagNodeNameCondition(findName), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode findElementHavingAttribute(String attName, boolean isRecursive) {
|
||||
return findElement( new TagNodeAttExistsCondition(attName), isRecursive );
|
||||
}
|
||||
|
||||
public List getElementListHavingAttribute(String attName, boolean isRecursive) {
|
||||
return getElementList( new TagNodeAttExistsCondition(attName), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode[] getElementsHavingAttribute(String attName, boolean isRecursive) {
|
||||
return getElements( new TagNodeAttExistsCondition(attName), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode findElementByAttValue(String attName, String attValue, boolean isRecursive, boolean isCaseSensitive) {
|
||||
return findElement( new TagNodeAttValueCondition(attName, attValue, isCaseSensitive), isRecursive );
|
||||
}
|
||||
|
||||
public List getElementListByAttValue(String attName, String attValue, boolean isRecursive, boolean isCaseSensitive) {
|
||||
return getElementList( new TagNodeAttValueCondition(attName, attValue, isCaseSensitive), isRecursive );
|
||||
}
|
||||
|
||||
public TagNode[] getElementsByAttValue(String attName, String attValue, boolean isRecursive, boolean isCaseSensitive) {
|
||||
return getElements( new TagNodeAttValueCondition(attName, attValue, isCaseSensitive), isRecursive );
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates XPath expression on give node. <br>
|
||||
* <em>
|
||||
* This is not fully supported XPath parser and evaluator.
|
||||
* Examples below show supported elements:
|
||||
* </em>
|
||||
* <code>
|
||||
* <ul>
|
||||
* <li>//div//a</li>
|
||||
* <li>//div//a[@id][@class]</li>
|
||||
* <li>/body/*[1]/@type</li>
|
||||
* <li>//div[3]//a[@id][@href='r/n4']</li>
|
||||
* <li>//div[last() >= 4]//./div[position() = last()])[position() > 22]//li[2]//a</li>
|
||||
* <li>//div[2]/@*[2]</li>
|
||||
* <li>data(//div//a[@id][@class])</li>
|
||||
* <li>//p/last()</li>
|
||||
* <li>//body//div[3][@class]//span[12.2<position()]/@id</li>
|
||||
* <li>data(//a['v' < @id])</li>
|
||||
* </ul>
|
||||
* </code>
|
||||
* @param xPathExpression
|
||||
* @return
|
||||
* @throws XPatherException
|
||||
*/
|
||||
public Object[] evaluateXPath(String xPathExpression) throws XPatherException {
|
||||
return new XPather(xPathExpression).evaluateAgainstNode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this node from the tree.
|
||||
* @return True if element is removed (if it is not root node).
|
||||
*/
|
||||
public boolean removeFromTree() {
|
||||
if (parent != null) {
|
||||
boolean existed = parent.removeChild(this);
|
||||
parent = null;
|
||||
return existed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specified child element from this node.
|
||||
* @param child
|
||||
* @return True if child object existed in the children list.
|
||||
*/
|
||||
public boolean removeChild(Object child) {
|
||||
return this.children.remove(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all children (subelements and text content).
|
||||
*/
|
||||
public void removeAllChildren() {
|
||||
this.children.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces specified child node with specified replacement node.
|
||||
* @param childToReplace Child node to be replaced
|
||||
* @param replacement Replacement node
|
||||
*/
|
||||
public void replaceChild(HtmlNode childToReplace, HtmlNode replacement) {
|
||||
if (replacement == null) {
|
||||
return;
|
||||
}
|
||||
ListIterator it = children.listIterator();
|
||||
while (it.hasNext()) {
|
||||
Object curr = it.next();
|
||||
if (curr == childToReplace) {
|
||||
it.set(replacement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param child Child to find index of
|
||||
* @return Index of the specified child node inside this node's children, -1 if node is not the child
|
||||
*/
|
||||
public int getChildIndex(HtmlNode child) {
|
||||
int index = 0;
|
||||
for (Object curr: children) {
|
||||
if (curr == child) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts specified node at specified position in array of children
|
||||
* @param index
|
||||
* @param childToAdd
|
||||
*/
|
||||
public void insertChild(int index, HtmlNode childToAdd) {
|
||||
children.add(index, childToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts specified node in the list of children before specified child
|
||||
* @param node Child before which to insert new node
|
||||
* @param nodeToInsert Node to be inserted at specified position
|
||||
*/
|
||||
public void insertChildBefore(HtmlNode node, HtmlNode nodeToInsert) {
|
||||
int index = getChildIndex(node);
|
||||
if (index >= 0) {
|
||||
insertChild(index, nodeToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts specified node in the list of children after specified child
|
||||
* @param node Child after which to insert new node
|
||||
* @param nodeToInsert Node to be inserted at specified position
|
||||
*/
|
||||
public void insertChildAfter(HtmlNode node, HtmlNode nodeToInsert) {
|
||||
int index = getChildIndex(node);
|
||||
if (index >= 0) {
|
||||
insertChild(index + 1, nodeToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
void addItemForMoving(BaseToken item) {
|
||||
if (itemsToMove == null) {
|
||||
itemsToMove = new ArrayList<BaseToken>();
|
||||
}
|
||||
|
||||
itemsToMove.add(item);
|
||||
}
|
||||
|
||||
List<BaseToken> getItemsToMove() {
|
||||
return itemsToMove;
|
||||
}
|
||||
|
||||
void setItemsToMove(List<BaseToken> itemsToMove) {
|
||||
this.itemsToMove = itemsToMove;
|
||||
}
|
||||
|
||||
boolean isFormed() {
|
||||
return isFormed;
|
||||
}
|
||||
|
||||
void setFormed(boolean isFormed) {
|
||||
this.isFormed = isFormed;
|
||||
}
|
||||
|
||||
void setFormed() {
|
||||
setFormed(true);
|
||||
}
|
||||
|
||||
void transformAttributes(TagTransformation tagTrans) {
|
||||
boolean isPreserveSourceAtts = tagTrans.isPreserveSourceAttributes();
|
||||
boolean hasAttTransforms = tagTrans.hasAttributeTransformations();
|
||||
if ( hasAttTransforms || !isPreserveSourceAtts) {
|
||||
Map<String, String> newAttributes = isPreserveSourceAtts ? new LinkedHashMap<String, String>(attributes) : new LinkedHashMap<String, String>();
|
||||
if (hasAttTransforms) {
|
||||
Map map = tagTrans.getAttributeTransformations();
|
||||
Iterator iterator = map.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) iterator.next();
|
||||
String attName = (String) entry.getKey();
|
||||
String template = (String) entry.getValue();
|
||||
if (template == null) {
|
||||
newAttributes.remove(attName);
|
||||
} else {
|
||||
String attValue = Utils.evaluateTemplate(template, attributes);
|
||||
newAttributes.put(attName, attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.attributes = newAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the tree and performs visitor's action on each node. It stops when it
|
||||
* finishes all the tree or when visitor returns false.
|
||||
* @param visitor TagNodeVisitor implementation
|
||||
*/
|
||||
public void traverse(TagNodeVisitor visitor) {
|
||||
traverseInternally(visitor);
|
||||
}
|
||||
|
||||
|
||||
private boolean traverseInternally(TagNodeVisitor visitor) {
|
||||
if (visitor != null) {
|
||||
boolean hasParent = parent != null;
|
||||
boolean toContinue = visitor.visit(parent, this);
|
||||
|
||||
if (!toContinue) {
|
||||
return false; // if visitor stops traversal
|
||||
} else if (hasParent && parent == null) {
|
||||
return true; // if this node is pruned from the tree during the visit, then don't go deeper
|
||||
}
|
||||
for (Object child: children.toArray()) { // make an array to avoid ConcurrentModificationException when some node is cut
|
||||
if (child instanceof TagNode) {
|
||||
toContinue = ((TagNode)child).traverseInternally(visitor);
|
||||
} else if (child instanceof ContentNode) {
|
||||
toContinue = visitor.visit(this, (ContentNode)child);
|
||||
} else if (child instanceof CommentNode) {
|
||||
toContinue = visitor.visit(this, (CommentNode)child);
|
||||
}
|
||||
if (!toContinue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collect all prefixes in namespace declarations up the path to the document root from the specified node
|
||||
* @param prefixes Set of prefixes to be collected
|
||||
*/
|
||||
void collectNamespacePrefixesOnPath(Set<String> prefixes) {
|
||||
Map<String, String> nsDeclarations = getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (String prefix: nsDeclarations.keySet()) {
|
||||
prefixes.add(prefix);
|
||||
}
|
||||
}
|
||||
if (parent != null) {
|
||||
parent.collectNamespacePrefixesOnPath(prefixes);
|
||||
}
|
||||
}
|
||||
|
||||
String getNamespaceURIOnPath(String nsPrefix) {
|
||||
if (nsDeclarations != null) {
|
||||
for (Map.Entry<String, String> nsEntry: nsDeclarations.entrySet()) {
|
||||
String currName = nsEntry.getKey();
|
||||
if ( currName.equals(nsPrefix) || ("".equals(currName) && nsPrefix == null) ) {
|
||||
return nsEntry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent != null) {
|
||||
return parent.getNamespaceURIOnPath(nsPrefix);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void serialize(Serializer serializer, Writer writer) throws IOException {
|
||||
serializer.serialize(this, writer);
|
||||
}
|
||||
|
||||
TagNode makeCopy() {
|
||||
TagNode copy = new TagNode(name);
|
||||
copy.attributes.putAll(attributes);
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* Defines action to be performed on TagNodes
|
||||
*/
|
||||
public interface TagNodeVisitor {
|
||||
|
||||
/**
|
||||
* Action to be performed on single node in the tree
|
||||
* @param parentNode Parent of tagNode
|
||||
* @param htmlNode node visited
|
||||
* @return True if tree traversal should be continued, false if it has to stop.
|
||||
*/
|
||||
public boolean visit(TagNode parentNode, HtmlNode htmlNode);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
|
||||
/**
|
||||
* <p>HTML tag token - descendants are start (TagNode) and end token (EndTagToken).</p>
|
||||
*/
|
||||
public abstract class TagToken implements BaseToken {
|
||||
|
||||
protected String name;
|
||||
|
||||
public TagToken() {
|
||||
}
|
||||
|
||||
public TagToken(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
abstract void setAttribute(String attName, String attValue);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Describes how specified tag is transformed to another one, or is ignored during parsing
|
||||
*/
|
||||
public class TagTransformation {
|
||||
|
||||
private String sourceTag;
|
||||
private String destTag;
|
||||
private boolean preserveSourceAttributes;
|
||||
private Map attributeTransformations;
|
||||
|
||||
/**
|
||||
* Creates new tag transformation from source tag to target tag specifying whether
|
||||
* source tag attributes are preserved.
|
||||
* @param sourceTag Name of the tag to be transformed.
|
||||
* @param destTag Name of tag to which source tag is to be transformed.
|
||||
* @param preserveSourceAttributes Tells whether source tag attributes are preserved in transformation.
|
||||
*/
|
||||
public TagTransformation(String sourceTag, String destTag, boolean preserveSourceAttributes) {
|
||||
this.sourceTag = sourceTag.toLowerCase();
|
||||
if (destTag == null) {
|
||||
this.destTag = null;
|
||||
} else {
|
||||
this.destTag = Utils.isValidXmlIdentifier(destTag) ? destTag.toLowerCase() : sourceTag;
|
||||
}
|
||||
this.preserveSourceAttributes = preserveSourceAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new tag transformation from source tag to target tag preserving
|
||||
* all source tag attributes.
|
||||
* @param sourceTag Name of the tag to be transformed.
|
||||
* @param destTag Name of tag to which source tag is to be transformed.
|
||||
*/
|
||||
public TagTransformation(String sourceTag, String destTag) {
|
||||
this(sourceTag, destTag, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new tag transformation in which specified tag will be skipped (ignored)
|
||||
* during parsing process.
|
||||
* @param sourceTag
|
||||
*/
|
||||
public TagTransformation(String sourceTag) {
|
||||
this(sourceTag, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new attribute transformation to this tag transformation. It tells how destination
|
||||
* attribute will look like. Small templating mechanism is used to describe attribute value:
|
||||
* all names between ${ and } inside the template are evaluated against source tag attributes.
|
||||
* That way one can make attribute values consist of mix of source tag attributes.
|
||||
*
|
||||
* @param targetAttName Name of the destination attribute
|
||||
* @param transformationDesc Template describing attribute value.
|
||||
*/
|
||||
public void addAttributeTransformation(String targetAttName, String transformationDesc) {
|
||||
if (attributeTransformations == null) {
|
||||
attributeTransformations = new LinkedHashMap();
|
||||
}
|
||||
attributeTransformations.put(targetAttName.toLowerCase(), transformationDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new attribute transformation in which destination attrbute will not exists
|
||||
* (simply removes it from list of attributes).
|
||||
* @param targetAttName
|
||||
*/
|
||||
public void addAttributeTransformation(String targetAttName) {
|
||||
addAttributeTransformation(targetAttName, null);
|
||||
}
|
||||
|
||||
boolean hasAttributeTransformations() {
|
||||
return attributeTransformations != null;
|
||||
}
|
||||
|
||||
String getSourceTag() {
|
||||
return sourceTag;
|
||||
}
|
||||
|
||||
String getDestTag() {
|
||||
return destTag;
|
||||
}
|
||||
|
||||
boolean isPreserveSourceAttributes() {
|
||||
return preserveSourceAttributes;
|
||||
}
|
||||
|
||||
Map getAttributeTransformations() {
|
||||
return attributeTransformations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>Common utilities.</p>
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
public static String VAR_START = "${";
|
||||
public static String VAR_END = "}";
|
||||
|
||||
public static final Map<Character, String> RESERVED_XML_CHARS = new HashMap<Character, String>();
|
||||
|
||||
static {
|
||||
RESERVED_XML_CHARS.put('&', "&");
|
||||
RESERVED_XML_CHARS.put('<', "<");
|
||||
RESERVED_XML_CHARS.put('>', ">");
|
||||
RESERVED_XML_CHARS.put('\"', """);
|
||||
RESERVED_XML_CHARS.put('\'', "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims specified string from left.
|
||||
* @param s
|
||||
*/
|
||||
public static String ltrim(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
int len = s.length();
|
||||
|
||||
while ( index < len && Character.isWhitespace(s.charAt(index)) ) {
|
||||
index++;
|
||||
}
|
||||
|
||||
return (index >= len) ? "" : s.substring(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims specified string from right.
|
||||
* @param s
|
||||
*/
|
||||
public static String rtrim(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int len = s.length();
|
||||
int index = len;
|
||||
|
||||
while ( index > 0 && Character.isWhitespace(s.charAt(index-1)) ) {
|
||||
index--;
|
||||
}
|
||||
|
||||
return (index <= 0) ? "" : s.substring(0, index);
|
||||
}
|
||||
|
||||
public static String getCharsetFromContentTypeString(String contentType) {
|
||||
if (contentType != null) {
|
||||
String pattern = "charset=([a-z\\d\\-]*)";
|
||||
Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(contentType);
|
||||
if (matcher.find()) {
|
||||
String charset = matcher.group(1);
|
||||
if (Charset.isSupported(charset)) {
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getCharsetFromContent(URL url) throws IOException {
|
||||
InputStream stream = url.openStream();
|
||||
byte chunk[] = new byte[2048];
|
||||
int bytesRead = stream.read(chunk);
|
||||
if (bytesRead > 0) {
|
||||
String startContent = new String(chunk);
|
||||
String pattern = "\\<meta\\s*http-equiv=[\\\"\\']content-type[\\\"\\']\\s*content\\s*=\\s*[\"']text/html\\s*;\\s*charset=([a-z\\d\\-]*)[\\\"\\'\\>]";
|
||||
Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(startContent);
|
||||
if (matcher.find()) {
|
||||
String charset = matcher.group(1);
|
||||
if (Charset.isSupported(charset)) {
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isHexadecimalDigit(char ch) {
|
||||
return Character.isDigit(ch) ||
|
||||
ch == 'A' || ch == 'a' || ch == 'B' || ch == 'b' || ch == 'C' || ch == 'c' ||
|
||||
ch == 'D' || ch == 'd' || ch == 'E' || ch == 'e' || ch == 'F' || ch == 'f';
|
||||
}
|
||||
|
||||
public static boolean isValidXmlChar(char ch) {
|
||||
return ((ch >= 0x20) && (ch <= 0xD7FF)) ||
|
||||
(ch == 0x9) ||
|
||||
(ch == 0xA) ||
|
||||
(ch == 0xD) ||
|
||||
((ch >= 0xE000) && (ch <= 0xFFFD)) ||
|
||||
((ch >= 0x10000) && (ch <= 0x10FFFF));
|
||||
}
|
||||
|
||||
public static boolean isReservedXmlChar(char ch) {
|
||||
return RESERVED_XML_CHARS.containsKey(ch);
|
||||
}
|
||||
|
||||
public static boolean isValidInt(String s, int radix) {
|
||||
try {
|
||||
Integer.parseInt(s, radix);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes XML string.
|
||||
* @param s String to be escaped
|
||||
* @param props Cleaner properties gover affect escaping behaviour
|
||||
* @param isDomCreation Tells if escaped content will be part of the DOM
|
||||
*/
|
||||
public static String escapeXml(String s, CleanerProperties props, boolean isDomCreation) {
|
||||
boolean advanced = props.isAdvancedXmlEscape();
|
||||
boolean recognizeUnicodeChars = props.isRecognizeUnicodeChars();
|
||||
boolean translateSpecialEntities = props.isTranslateSpecialEntities();
|
||||
|
||||
if (s != null) {
|
||||
int len = s.length();
|
||||
StringBuilder result = new StringBuilder(len);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
if (ch == '&') {
|
||||
if ( (advanced || recognizeUnicodeChars) && (i < len-2) && (s.charAt(i+1) == '#') ) {
|
||||
boolean isHex = Character.toLowerCase(s.charAt(i+2)) == 'x';
|
||||
int charIndex = i + (isHex ? 3 : 2);
|
||||
int radix = isHex ? 16 : 10;
|
||||
String unicode = "";
|
||||
while (charIndex < len) {
|
||||
char currCh = s.charAt(charIndex);
|
||||
if (currCh == ';') {
|
||||
break;
|
||||
} else if (isValidInt(unicode + currCh, radix)) {
|
||||
unicode += currCh;
|
||||
charIndex++;
|
||||
} else {
|
||||
charIndex--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidInt(unicode, radix)) {
|
||||
char unicodeChar = (char)Integer.parseInt(unicode, radix);
|
||||
if ( !isValidXmlChar(unicodeChar) ) {
|
||||
i = charIndex;
|
||||
} else if ( !isReservedXmlChar(unicodeChar) ) {
|
||||
result.append( recognizeUnicodeChars ? String.valueOf(unicodeChar) : "&#" + unicode + ";" );
|
||||
i = charIndex;
|
||||
} else {
|
||||
i = charIndex;
|
||||
result.append("&#" + unicode + ";");
|
||||
}
|
||||
} else {
|
||||
result.append("&");
|
||||
}
|
||||
} else {
|
||||
if (translateSpecialEntities) {
|
||||
// get minimal following sequence required to recognize some special entitiy
|
||||
String seq = s.substring(i, i + Math.min(SpecialEntity.getMaxEntityLength() + 2, len - i));
|
||||
int semiIndex = seq.indexOf(';');
|
||||
if (semiIndex > 0) {
|
||||
String entityKey = seq.substring(1, semiIndex);
|
||||
SpecialEntity entity = SpecialEntity.getEntity(entityKey);
|
||||
if (entity != null) {
|
||||
result.append(props.isTransSpecialEntitiesToNCR() ? entity.getDecimalNCR() : entity.getCharacter());
|
||||
i += entityKey.length() + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (advanced) {
|
||||
String sub = s.substring(i);
|
||||
boolean isReservedSeq = false;
|
||||
for (Map.Entry<Character, String> entry: RESERVED_XML_CHARS.entrySet()) {
|
||||
String seq = entry.getValue();
|
||||
if ( sub.startsWith(seq) ) {
|
||||
result.append( isDomCreation ? entry.getKey() : (props.transResCharsToNCR ? "&#" + (int)entry.getKey() + ";" : seq) );
|
||||
i += seq.length() - 1;
|
||||
isReservedSeq = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isReservedSeq) {
|
||||
result.append( isDomCreation ? "&" : (props.transResCharsToNCR ? "&#" + (int)'&' + ";" : RESERVED_XML_CHARS.get('&')) );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append("&");
|
||||
}
|
||||
} else if (isReservedXmlChar(ch)) {
|
||||
result.append( props.transResCharsToNCR ? "&#" + (int)ch + ";" : (isDomCreation ? ch : RESERVED_XML_CHARS.get(ch)) );
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether specified object's string representation is empty string (containing of only whitespaces).
|
||||
* @param object Object whose string representation is checked
|
||||
* @return true, if empty string, false otherwise
|
||||
*/
|
||||
public static boolean isWhitespaceString(Object object) {
|
||||
if (object != null) {
|
||||
String s = object.toString();
|
||||
return s != null && "".equals(s.trim());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified character can be part of xml identifier (tag name of attribute name)
|
||||
* and is not standard identifier character.
|
||||
* @param ch Character to be checked
|
||||
* @return True if it can be part of xml identifier
|
||||
*/
|
||||
public static boolean isIdentifierHelperChar(char ch) {
|
||||
return ':' == ch || '.' == ch || '-' == ch || '_' == ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chacks whether specified string can be valid tag name or attribute name in xml.
|
||||
* @param s String to be checked
|
||||
* @return True if string is valid xml identifier, false otherwise
|
||||
*/
|
||||
public static boolean isValidXmlIdentifier(String s) {
|
||||
if (s != null) {
|
||||
int len = s.length();
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
char ch = s.charAt(i);
|
||||
if ( (i == 0 && !Character.isUnicodeIdentifierStart(ch) && ch != '_') ||
|
||||
(!Character.isUnicodeIdentifierStart(ch) && !Character.isDigit(ch) && !Utils.isIdentifierHelperChar(ch)) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param o
|
||||
* @return True if specified string is null of contains only whitespace characters
|
||||
*/
|
||||
public static boolean isEmptyString(Object o) {
|
||||
return o == null || "".equals(o.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates string template for specified map of variables. Template string can contain
|
||||
* dynamic parts in the form of ${VARNAME}. Each such part is replaced with value of the
|
||||
* variable if such exists in the map, or with empty string otherwise.
|
||||
*
|
||||
* @param template Template string
|
||||
* @param variables Map of variables (can be null)
|
||||
* @return Evaluated string
|
||||
*/
|
||||
public static String evaluateTemplate(String template, Map variables) {
|
||||
if (template == null) {
|
||||
return template;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
int startIndex = template.indexOf(VAR_START);
|
||||
int endIndex = -1;
|
||||
|
||||
while (startIndex >= 0 && startIndex < template.length()) {
|
||||
result.append( template.substring(endIndex + 1, startIndex) );
|
||||
endIndex = template.indexOf(VAR_END, startIndex);
|
||||
|
||||
if (endIndex > startIndex) {
|
||||
String varName = template.substring(startIndex + VAR_START.length(), endIndex);
|
||||
Object resultObj = variables != null ? variables.get(varName.toLowerCase()) : "";
|
||||
result.append( resultObj == null ? "" : resultObj.toString() );
|
||||
}
|
||||
|
||||
startIndex = template.indexOf( VAR_START, Math.max(endIndex + VAR_END.length(), startIndex + 1) );
|
||||
}
|
||||
|
||||
result.append( template.substring(endIndex + 1) );
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String[] tokenize(String s, String delimiters) {
|
||||
if (s == null) {
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(s, delimiters);
|
||||
String result[] = new String[tokenizer.countTokens()];
|
||||
int index = 0;
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
result[index++] = tokenizer.nextToken();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void updateTagTransformations(CleanerTransformations transformations, String key, String value) {
|
||||
int index = key.indexOf('.');
|
||||
|
||||
// new tag transformation case (tagname[=destname[,preserveatts]])
|
||||
if (index <= 0) {
|
||||
String destTag = null;
|
||||
boolean preserveSourceAtts = true;
|
||||
if (value != null) {
|
||||
String[] tokens = tokenize(value, ",;");
|
||||
if (tokens.length > 0) {
|
||||
destTag = tokens[0];
|
||||
}
|
||||
if (tokens.length > 1) {
|
||||
preserveSourceAtts = "true".equalsIgnoreCase(tokens[1]) ||
|
||||
"yes".equalsIgnoreCase(tokens[1]) ||
|
||||
"1".equals(tokens[1]);
|
||||
}
|
||||
}
|
||||
TagTransformation newTagTrans = new TagTransformation(key, destTag, preserveSourceAtts);
|
||||
transformations.addTransformation(newTagTrans);
|
||||
} else { // attribute transformation description
|
||||
String[] parts = tokenize(key, ".");
|
||||
String tagName = parts[0];
|
||||
TagTransformation trans = transformations.getTransformation(tagName);
|
||||
if (trans != null) {
|
||||
trans.addAttributeTransformation(parts[1], value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified link is full URL.
|
||||
*
|
||||
* @param link
|
||||
* @return True, if full URl, false otherwise.
|
||||
*/
|
||||
public static boolean isFullUrl(String link) {
|
||||
if (link == null) {
|
||||
return false;
|
||||
}
|
||||
link = link.trim().toLowerCase();
|
||||
return link.startsWith("http://") || link.startsWith("https://") || link.startsWith("file://");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates full URL for specified page URL and link
|
||||
* which could be full, absolute or relative like there can
|
||||
* be found in A or IMG tags.
|
||||
*/
|
||||
public static String fullUrl(String pageUrl, String link) {
|
||||
if (isFullUrl(link)) {
|
||||
return link;
|
||||
} else if (link != null && link.startsWith("?")) {
|
||||
int qindex = pageUrl.indexOf('?');
|
||||
int len = pageUrl.length();
|
||||
if (qindex < 0) {
|
||||
return pageUrl + link;
|
||||
} else if (qindex == len - 1) {
|
||||
return pageUrl.substring(0, len - 1) + link;
|
||||
} else {
|
||||
return pageUrl + "&" + link.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLinkAbsolute = link.startsWith("/");
|
||||
|
||||
if (!isFullUrl(pageUrl)) {
|
||||
pageUrl = "http://" + pageUrl;
|
||||
}
|
||||
|
||||
int slashIndex = isLinkAbsolute ? pageUrl.indexOf("/", 8) : pageUrl.lastIndexOf("/");
|
||||
if (slashIndex <= 8) {
|
||||
pageUrl += "/";
|
||||
} else {
|
||||
pageUrl = pageUrl.substring(0, slashIndex + 1);
|
||||
}
|
||||
|
||||
return isLinkAbsolute ? pageUrl + link.substring(1) : pageUrl + link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return For xml element name or attribute name returns prefix (part before :) or null if there is no prefix
|
||||
*/
|
||||
public static String getXmlNSPrefix(String name) {
|
||||
int colIndex = name.indexOf(':');
|
||||
if (colIndex > 0) {
|
||||
return name.substring(0, colIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return For xml element name or attribute name returns name after prefix (part after :)
|
||||
*/
|
||||
public static String getXmlName(String name) {
|
||||
int colIndex = name.indexOf(':');
|
||||
if (colIndex > 0 && colIndex < name.length() - 1) {
|
||||
return name.substring(colIndex + 1);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Utility for searching cleaned document tree with XPath expressions.</p>
|
||||
* Examples of supported axes:
|
||||
* <code>
|
||||
* <ul>
|
||||
* <li>//div//a</li>
|
||||
* <li>//div//a[@id][@class]</li>
|
||||
* <li>/body/*[1]/@type</li>
|
||||
* <li>//div[3]//a[@id][@href='r/n4']</li>
|
||||
* <li>//div[last() >= 4]//./div[position() = last()])[position() > 22]//li[2]//a</li>
|
||||
* <li>//div[2]/@*[2]</li>
|
||||
* <li>data(//div//a[@id][@class])</li>
|
||||
* <li>//p/last()</li>
|
||||
* <li>//body//div[3][@class]//span[12.2<position()]/@id</li>
|
||||
* <li>data(//a['v' < @id])</li>
|
||||
* </ul>
|
||||
* </code>
|
||||
*/
|
||||
public class XPather {
|
||||
|
||||
// array of basic tokens of which XPath expression is made
|
||||
private String tokenArray[];
|
||||
|
||||
/**
|
||||
* Constructor - creates XPather instance with specified XPath expression.
|
||||
* @param expression
|
||||
*/
|
||||
public XPather(String expression) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(expression, "/()[]\"'=<>", true);
|
||||
int tokenCount = tokenizer.countTokens();
|
||||
tokenArray = new String[tokenCount];
|
||||
|
||||
int index = 0;
|
||||
|
||||
// this is not real XPath compiler, rather simple way to recognize basic XPaths expressions
|
||||
// and interpret them against some TagNode instance.
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
tokenArray[index++] = tokenizer.nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main public method for this class - a way to execute XPath expression against
|
||||
* specified TagNode instance.
|
||||
* @param node
|
||||
*/
|
||||
public Object[] evaluateAgainstNode(TagNode node) throws XPatherException {
|
||||
if (node == null) {
|
||||
throw new XPatherException("Cannot evaluate XPath expression against null value!");
|
||||
}
|
||||
|
||||
Collection collectionResult = evaluateAgainst(singleton(node), 0, tokenArray.length - 1, false, 1, 0, false, null);
|
||||
Object[] array = new Object[collectionResult.size()];
|
||||
|
||||
Iterator iterator = collectionResult.iterator();
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
array[index++] = iterator.next();
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private void throwStandardException() throws XPatherException {
|
||||
throw new XPatherException();
|
||||
}
|
||||
|
||||
private Collection evaluateAgainst(Collection object,
|
||||
int from,
|
||||
int to,
|
||||
boolean isRecursive,
|
||||
int position,
|
||||
int last,
|
||||
boolean isFilterContext,
|
||||
Collection filterSource) throws XPatherException {
|
||||
if (from >= 0 && to < tokenArray.length && from <= to) {
|
||||
if ("".equals(tokenArray[from].trim())) {
|
||||
return evaluateAgainst(object, from + 1, to, isRecursive, position, last, isFilterContext, filterSource);
|
||||
} else if (isToken("(", from)) {
|
||||
int closingBracket = findClosingIndex(from, to);
|
||||
if (closingBracket > 0) {
|
||||
Collection value = evaluateAgainst(object, from + 1, closingBracket - 1, false, position, last, isFilterContext, filterSource);
|
||||
return evaluateAgainst(value, closingBracket + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if (isToken("[", from)) {
|
||||
int closingBracket = findClosingIndex(from, to);
|
||||
if (closingBracket > 0 && object instanceof Collection) {
|
||||
Collection value = filterByCondition(object, from + 1, closingBracket - 1);
|
||||
return evaluateAgainst(value, closingBracket + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if (isToken("\"", from) || isToken("'", from)) { // string constant
|
||||
int closingQuote = findClosingIndex(from, to);
|
||||
if (closingQuote > from) {
|
||||
Collection value = singleton( flatten(from + 1, closingQuote - 1) );
|
||||
return evaluateAgainst(value, closingQuote + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if ( (isToken("=", from) || isToken("<", from) || isToken(">", from)) && isFilterContext ) { // operator inside filter
|
||||
boolean logicValue;
|
||||
if ( isToken("=", from + 1) && (isToken("<", from) || isToken(">", from)) ) {
|
||||
Collection secondObject = evaluateAgainst(filterSource, from + 2, to, false, position, last, isFilterContext, filterSource);
|
||||
logicValue = evaluateLogic(object, secondObject, tokenArray[from] + tokenArray[from + 1]);
|
||||
} else {
|
||||
Collection secondObject = evaluateAgainst(filterSource, from + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
logicValue = evaluateLogic(object, secondObject, tokenArray[from]);
|
||||
}
|
||||
return singleton(new Boolean(logicValue));
|
||||
} else if (isToken("/", from)) { // children of the node
|
||||
boolean goRecursive = isToken("/", from + 1);
|
||||
if (goRecursive) {
|
||||
from++;
|
||||
}
|
||||
if ( from < to ) {
|
||||
int toIndex = findClosingIndex(from, to) - 1;
|
||||
if (toIndex <= from) {
|
||||
toIndex = to;
|
||||
}
|
||||
Collection value = evaluateAgainst(object, from + 1, toIndex, goRecursive, 1, last, isFilterContext, filterSource);
|
||||
return evaluateAgainst(value, toIndex + 1, to, false, 1, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if (isFunctionCall(from, to)) {
|
||||
int closingBracketIndex = findClosingIndex(from + 1, to);
|
||||
Collection funcValue = evaluateFunction(object, from, to, position, last, isFilterContext);
|
||||
return evaluateAgainst(funcValue, closingBracketIndex + 1, to, false, 1, last, isFilterContext, filterSource);
|
||||
} else if (isValidInteger(tokenArray[from])) {
|
||||
Collection value = singleton(new Integer(tokenArray[from]));
|
||||
return evaluateAgainst(value, from + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else if (isValidDouble(tokenArray[from])) {
|
||||
Collection value = singleton(new Double(tokenArray[from]));
|
||||
return evaluateAgainst(value, from + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
return getElementsByName(object, from, to, isRecursive, isFilterContext);
|
||||
}
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
|
||||
throw new XPatherException();
|
||||
}
|
||||
|
||||
private String flatten(int from, int to) {
|
||||
if (from <= to) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
for (int i = from; i <= to; i++) {
|
||||
result.append(tokenArray[i]);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private boolean isValidInteger(String s) {
|
||||
try {
|
||||
Integer.parseInt(s);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidDouble(String s) {
|
||||
try {
|
||||
Double.parseDouble(s);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given string is valid identifier.
|
||||
* @param s
|
||||
*/
|
||||
private boolean isIdentifier(String s) {
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s = s.trim();
|
||||
if (s.length() > 0) {
|
||||
if ( !Character.isLetter(s.charAt(0)) ) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 1; i < s.length(); i++) {
|
||||
final char ch = s.charAt(i);
|
||||
if ( ch != '_' && ch != '-' && !Character.isLetterOrDigit(ch) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if tokens in specified range represents valid function call.
|
||||
* @param from
|
||||
* @param to
|
||||
* @return True if it is valid function call, false otherwise.
|
||||
*/
|
||||
private boolean isFunctionCall(int from, int to) {
|
||||
if ( !isIdentifier(tokenArray[from]) && !isToken("(", from + 1) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return findClosingIndex(from + 1, to) > from + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates specified function.
|
||||
* Currently, following XPath functions are supported: last, position, text, count, data
|
||||
* @param source
|
||||
* @param from
|
||||
* @param to
|
||||
* @param position
|
||||
* @param last
|
||||
* @return Collection as the result of evaluation.
|
||||
*/
|
||||
private Collection evaluateFunction(Collection source,
|
||||
int from,
|
||||
int to,
|
||||
int position,
|
||||
int last,
|
||||
boolean isFilterContext) throws XPatherException {
|
||||
String name = tokenArray[from].trim();
|
||||
ArrayList result = new ArrayList();
|
||||
|
||||
final int size = source.size();
|
||||
Iterator iterator = source.iterator();
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
Object curr = iterator.next();
|
||||
index++;
|
||||
if ( "last".equals(name) ) {
|
||||
result.add( new Integer(isFilterContext ? last : size) );
|
||||
} else if ( "position".equals(name) ) {
|
||||
result.add( new Integer(isFilterContext ? position : index) );
|
||||
} else if ( "text".equals(name) ) {
|
||||
if (curr instanceof TagNode) {
|
||||
result.add( ((TagNode)curr).getText() );
|
||||
} else if (curr instanceof String) {
|
||||
result.add( curr.toString() );
|
||||
}
|
||||
} else if ( "count".equals(name) ) {
|
||||
Collection argumentEvaluated =
|
||||
evaluateAgainst(source, from + 2, to - 1, false, position, 0, isFilterContext, null);
|
||||
result.add( new Integer(argumentEvaluated.size()) );
|
||||
} else if ( "data".equals(name) ) {
|
||||
Collection argumentEvaluated = evaluateAgainst(source, from + 2, to - 1, false, position, 0, isFilterContext, null);
|
||||
Iterator it = argumentEvaluated.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object elem = it.next();
|
||||
if (elem instanceof TagNode) {
|
||||
result.add( ((TagNode)elem).getText() );
|
||||
} else if (elem instanceof String) {
|
||||
result.add( elem.toString() );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new XPatherException("Unknown function " + name + "!");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter nodes satisfying the condition
|
||||
* @param source
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
private Collection filterByCondition(Collection source, int from, int to) throws XPatherException {
|
||||
ArrayList result = new ArrayList();
|
||||
Iterator iterator = source.iterator();
|
||||
int index = 0;
|
||||
int size = source.size();
|
||||
while (iterator.hasNext()) {
|
||||
Object curr = iterator.next();
|
||||
index++;
|
||||
|
||||
ArrayList logicValueList = new ArrayList(evaluateAgainst(singleton(curr), from, to, false, index, size, true, singleton(curr)));
|
||||
if (logicValueList.size() >= 1) {
|
||||
Object first = logicValueList.get(0);
|
||||
if (first instanceof Boolean) {
|
||||
if ( ((Boolean)first).booleanValue() ) {
|
||||
result.add(curr);
|
||||
}
|
||||
} else if (first instanceof Integer) {
|
||||
if ( ((Integer)first).intValue() == index ) {
|
||||
result.add(curr);
|
||||
}
|
||||
} else {
|
||||
result.add(curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isToken(String token, int index) {
|
||||
int len = tokenArray.length;
|
||||
return index >= 0 && index < len && tokenArray[index].trim().equals(token.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from
|
||||
* @param to
|
||||
* @return matching closing index in the token array for the current token, or -1 if there is
|
||||
* no closing token within expected bounds.
|
||||
*/
|
||||
private int findClosingIndex(int from, int to) {
|
||||
if (from < to) {
|
||||
String currToken = tokenArray[from];
|
||||
|
||||
if ("\"".equals(currToken)) {
|
||||
for (int i = from + 1; i <= to; i++) {
|
||||
if ("\"".equals(tokenArray[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else if ("'".equals(currToken)) {
|
||||
for (int i = from + 1; i <= to; i++) {
|
||||
if ("'".equals(tokenArray[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else if ( "(".equals(currToken) || "[".equals(currToken) || "/".equals(currToken) ) {
|
||||
boolean isQuoteClosed = true;
|
||||
boolean isAposClosed = true;
|
||||
int brackets = "(".equals(currToken) ? 1 : 0;
|
||||
int angleBrackets = "[".equals(currToken) ? 1 : 0;
|
||||
int slashes = "/".equals(currToken) ? 1 : 0;
|
||||
for (int i = from + 1; i <= to; i++) {
|
||||
if ( "\"".equals(tokenArray[i]) ) {
|
||||
isQuoteClosed = !isQuoteClosed;
|
||||
} else if ( "'".equals(tokenArray[i]) ) {
|
||||
isAposClosed = !isAposClosed;
|
||||
} else if ( "(".equals(tokenArray[i]) && isQuoteClosed && isAposClosed ) {
|
||||
brackets++;
|
||||
} else if ( ")".equals(tokenArray[i]) && isQuoteClosed && isAposClosed ) {
|
||||
brackets--;
|
||||
} else if ( "[".equals(tokenArray[i]) && isQuoteClosed && isAposClosed ) {
|
||||
angleBrackets++;
|
||||
} else if ( "]".equals(tokenArray[i]) && isQuoteClosed && isAposClosed ) {
|
||||
angleBrackets--;
|
||||
} else if ( "/".equals(tokenArray[i]) && isQuoteClosed && isAposClosed && brackets == 0 && angleBrackets == 0) {
|
||||
slashes--;
|
||||
}
|
||||
|
||||
if (isQuoteClosed && isAposClosed && brackets == 0 && angleBrackets == 0 && slashes == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if token is attribute (starts with @)
|
||||
* @param token
|
||||
*/
|
||||
private boolean isAtt(String token) {
|
||||
return token != null && token.length() > 1 && token.startsWith("@");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates one-element collection for the specified object.
|
||||
* @param element
|
||||
*/
|
||||
private Collection singleton(Object element) {
|
||||
ArrayList result = new ArrayList();
|
||||
result.add(element);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given source collection and specified name, returns collection of subnodes
|
||||
* or attribute values.
|
||||
* @param source
|
||||
* @param from
|
||||
* @param to
|
||||
* @param isRecursive
|
||||
* @return Colection of TagNode instances or collection of String instances.
|
||||
*/
|
||||
private Collection getElementsByName(Collection source, int from, int to, boolean isRecursive, boolean isFilterContext) throws XPatherException {
|
||||
String name = tokenArray[from].trim();
|
||||
|
||||
if (isAtt(name)) {
|
||||
name = name.substring(1);
|
||||
Collection result = new ArrayList();
|
||||
Collection nodes;
|
||||
if (isRecursive) {
|
||||
nodes = new LinkedHashSet();
|
||||
Iterator iterator = source.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Object next = iterator.next();
|
||||
if (next instanceof TagNode) {
|
||||
TagNode node = (TagNode) next;
|
||||
nodes.addAll( node.getAllElementsList(true) );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodes = source;
|
||||
}
|
||||
|
||||
Iterator iterator = nodes.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Object next = iterator.next();
|
||||
if (next instanceof TagNode) {
|
||||
TagNode node = (TagNode) next;
|
||||
if ("*".equals(name)) {
|
||||
result.addAll( evaluateAgainst(node.getAttributes().values(), from + 1, to, false, 1, 1, isFilterContext, null) );
|
||||
} else {
|
||||
String attValue = node.getAttributeByName(name);
|
||||
if (attValue != null) {
|
||||
result.addAll( evaluateAgainst(singleton(attValue), from + 1, to, false, 1, 1, isFilterContext, null) );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
Collection result = new LinkedHashSet();
|
||||
Iterator iterator = source.iterator();
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
final Object next = iterator.next();
|
||||
if (next instanceof TagNode) {
|
||||
TagNode node = (TagNode) next;
|
||||
index++;
|
||||
boolean isSelf = ".".equals(name);
|
||||
boolean isParent = "..".equals(name);
|
||||
boolean isAll = "*".equals(name);
|
||||
|
||||
Collection subnodes;
|
||||
if (isSelf) {
|
||||
subnodes = singleton(node);
|
||||
} else if (isParent) {
|
||||
TagNode parent = node.getParent();
|
||||
subnodes = parent != null ? singleton(parent) : new ArrayList();
|
||||
} else {
|
||||
subnodes = isAll ? node.getChildTagList() : node.getElementListByName(name, false);
|
||||
}
|
||||
|
||||
LinkedHashSet nodeSet = new LinkedHashSet(subnodes);
|
||||
Collection refinedSubnodes = evaluateAgainst(nodeSet, from + 1, to, false, index, nodeSet.size(), isFilterContext, null);
|
||||
|
||||
if (isRecursive) {
|
||||
List childTags = node.getChildTagList();
|
||||
if (isSelf || isParent || isAll) {
|
||||
result.addAll(refinedSubnodes);
|
||||
}
|
||||
Iterator childIterator = childTags.iterator();
|
||||
while (childIterator.hasNext()) {
|
||||
TagNode childTag = (TagNode) childIterator.next();
|
||||
Collection childrenByName = getElementsByName(singleton(childTag), from, to, isRecursive, isFilterContext);
|
||||
if ( !isSelf && !isParent && !isAll && refinedSubnodes.contains(childTag) ) {
|
||||
result.add(childTag);
|
||||
}
|
||||
result.addAll(childrenByName);
|
||||
}
|
||||
} else {
|
||||
result.addAll(refinedSubnodes);
|
||||
}
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates logic operation on two collections.
|
||||
* @param first
|
||||
* @param second
|
||||
* @param logicOperator
|
||||
* @return Result of logic operation
|
||||
*/
|
||||
private boolean evaluateLogic(Collection first, Collection second, String logicOperator) {
|
||||
if (first == null || first.size() == 0 || second == null || second.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
Object elem1 = first.iterator().next();
|
||||
Object elem2 = second.iterator().next();
|
||||
if (elem1 instanceof Number && elem2 instanceof Number) {
|
||||
double d1 = ((Number)elem1).doubleValue();
|
||||
double d2 = ((Number)elem2).doubleValue();
|
||||
if ("=".equals(logicOperator)) {
|
||||
return d1 == d2;
|
||||
} else if ("<".equals(logicOperator)) {
|
||||
return d1 < d2;
|
||||
} else if (">".equals(logicOperator)) {
|
||||
return d1 > d2;
|
||||
} else if ("<=".equals(logicOperator)) {
|
||||
return d1 <= d2;
|
||||
} else if (">=".equals(logicOperator)) {
|
||||
return d1 >= d2;
|
||||
}
|
||||
} else {
|
||||
String s1 = toText(elem1);
|
||||
String s2 = toText(elem2);
|
||||
int result = s1.compareTo(s2);
|
||||
if ("=".equals(logicOperator)) {
|
||||
return result == 0;
|
||||
} else if ("<".equals(logicOperator)) {
|
||||
return result < 0;
|
||||
} else if (">".equals(logicOperator)) {
|
||||
return result > 0;
|
||||
} else if ("<=".equals(logicOperator)) {
|
||||
return result <= 0;
|
||||
} else if (">=".equals(logicOperator)) {
|
||||
return result >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String toText(Object o) {
|
||||
if (o == null) {
|
||||
return "";
|
||||
} if (o instanceof TagNode) {
|
||||
return ((TagNode)o).getText().toString();
|
||||
} else {
|
||||
return o.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Exception that could occure during XPather evaluation.</p>
|
||||
*/
|
||||
public class XPatherException extends Exception {
|
||||
|
||||
public XPatherException() {
|
||||
this("Error in evaluating XPath expression!");
|
||||
}
|
||||
|
||||
public XPatherException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public XPatherException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public XPatherException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>Abstract XML serializer - contains common logic for descendants.</p>
|
||||
*/
|
||||
public abstract class XmlSerializer extends Serializer {
|
||||
|
||||
protected XmlSerializer(CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToStream() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToStream(TagNode tagNode, OutputStream out, String charset) throws IOException {
|
||||
super.writeToStream(tagNode, out, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToStream() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToStream(TagNode tagNode, OutputStream out) throws IOException {
|
||||
super.writeToStream(tagNode, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToFile() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToFile(TagNode tagNode, String fileName, String charset) throws IOException {
|
||||
super.writeToFile(tagNode, fileName, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToFile() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToFile(TagNode tagNode, String fileName) throws IOException {
|
||||
super.writeToFile(tagNode, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAsString() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getXmlAsString(TagNode tagNode, String charset) throws IOException {
|
||||
return super.getAsString(tagNode, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAsString() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getXmlAsString(TagNode tagNode) throws IOException {
|
||||
return super.getAsString(tagNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use write() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXml(TagNode tagNode, Writer writer, String charset) throws IOException {
|
||||
super.write(tagNode, writer, charset);
|
||||
}
|
||||
|
||||
protected String escapeXml(String xmlContent) {
|
||||
return Utils.escapeXml(xmlContent, props, false);
|
||||
}
|
||||
|
||||
protected boolean dontEscape(TagNode tagNode) {
|
||||
return props.isUseCdataForScriptAndStyle() && isScriptOrStyle(tagNode);
|
||||
}
|
||||
|
||||
protected boolean isMinimizedTagSyntax(TagNode tagNode) {
|
||||
final TagInfo tagInfo = props.getTagInfoProvider().getTagInfo(tagNode.getName());
|
||||
return tagNode.getChildren().size() == 0 &&
|
||||
( props.isUseEmptyElementTags() || (tagInfo != null && tagInfo.isEmptyTag()) );
|
||||
}
|
||||
|
||||
protected void serializeOpenTag(TagNode tagNode, Writer writer, boolean newLine) throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean nsAware = props.isNamespacesAware();
|
||||
|
||||
Set<String> definedNSPrefixes = null;
|
||||
Set<String> additionalNSDeclNeeded = null;
|
||||
|
||||
String tagPrefix = Utils.getXmlNSPrefix(tagName);
|
||||
if (tagPrefix != null) {
|
||||
if (nsAware) {
|
||||
definedNSPrefixes = new HashSet<String>();
|
||||
tagNode.collectNamespacePrefixesOnPath(definedNSPrefixes);
|
||||
if ( !definedNSPrefixes.contains(tagPrefix) ) {
|
||||
additionalNSDeclNeeded = new TreeSet<String>();
|
||||
additionalNSDeclNeeded.add(tagPrefix);
|
||||
}
|
||||
} else {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
writer.write("<" + tagName);
|
||||
|
||||
// write attributes
|
||||
for (Map.Entry<String, String> entry: tagNode.getAttributes().entrySet()) {
|
||||
String attName = entry.getKey();
|
||||
String attPrefix = Utils.getXmlNSPrefix(attName);
|
||||
if (attPrefix != null) {
|
||||
if (nsAware) {
|
||||
// collect used namespace prefixes in attributes in order to explicitly define
|
||||
// ns declaration if needed; otherwise it would be ill-formed xml
|
||||
if (definedNSPrefixes == null) {
|
||||
definedNSPrefixes = new HashSet<String>();
|
||||
tagNode.collectNamespacePrefixesOnPath(definedNSPrefixes);
|
||||
}
|
||||
if ( !definedNSPrefixes.contains(attPrefix) ) {
|
||||
if (additionalNSDeclNeeded == null) {
|
||||
additionalNSDeclNeeded = new TreeSet<String>();
|
||||
}
|
||||
additionalNSDeclNeeded.add(attPrefix);
|
||||
}
|
||||
} else {
|
||||
attName = Utils.getXmlName(attName);
|
||||
}
|
||||
}
|
||||
writer.write(" " + attName + "=\"" + escapeXml(entry.getValue()) + "\"");
|
||||
}
|
||||
|
||||
// write namespace declarations
|
||||
if (nsAware) {
|
||||
Map<String, String> nsDeclarations = tagNode.getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (Map.Entry<String, String> entry: nsDeclarations.entrySet()) {
|
||||
String prefix = entry.getKey();
|
||||
String att = "xmlns";
|
||||
if (prefix.length() > 0) {
|
||||
att += ":" + prefix;
|
||||
}
|
||||
writer.write(" " + att + "=\"" + escapeXml(entry.getValue()) + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write additional namespace declarations needed for this tag in order xml to be well-formed
|
||||
if (additionalNSDeclNeeded != null) {
|
||||
for (String prefix: additionalNSDeclNeeded) {
|
||||
writer.write(" xmlns:" + prefix + "=\"" + prefix + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
if ( isMinimizedTagSyntax(tagNode) ) {
|
||||
writer.write(" />");
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
} else if (dontEscape(tagNode)) {
|
||||
writer.write("><![CDATA[");
|
||||
} else {
|
||||
writer.write(">");
|
||||
}
|
||||
}
|
||||
|
||||
protected void serializeEndTag(TagNode tagNode, Writer writer, boolean newLine) throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dontEscape(tagNode)) {
|
||||
writer.write("]]>");
|
||||
}
|
||||
|
||||
if (Utils.getXmlNSPrefix(tagName) != null && !props.isNamespacesAware()) {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
writer.write( "</" + tagName + ">" );
|
||||
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Base token interface. Tokens are individual entities recognized by HTML
|
||||
* parser.
|
||||
* </p>
|
||||
*/
|
||||
public interface BaseToken {
|
||||
void serialize(Serializer serializer, Writer writer) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Broswer compact XML serializer - creates resulting XML by stripping
|
||||
* whitespaces wherever possible, but preserving single whitespace where at
|
||||
* least one exists. This behaviour is well suited for web-browsers, which
|
||||
* usualy treat multiple whitespaces as single one, but make diffrence between
|
||||
* single whitespace and empty text.
|
||||
* </p>
|
||||
*/
|
||||
public class BrowserCompactXmlSerializer extends XmlSerializer {
|
||||
public BrowserCompactXmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
final List<Object> tagChildren = tagNode.getChildren();
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
final ListIterator<Object> childrenIt = tagChildren.listIterator();
|
||||
while (childrenIt.hasNext()) {
|
||||
final Object item = childrenIt.next();
|
||||
if (item instanceof ContentNode) {
|
||||
String content = item.toString();
|
||||
final boolean startsWithSpace = content.length() > 0 && Character.isWhitespace(content.charAt(0));
|
||||
final boolean endsWithSpace = content.length() > 1
|
||||
&& Character.isWhitespace(content.charAt(content.length() - 1));
|
||||
content = dontEscape(tagNode) ? content.trim().replaceAll("]]>", "]]>") : escapeXml(content
|
||||
.trim());
|
||||
|
||||
if (startsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
|
||||
if (content.length() != 0) {
|
||||
writer.write(content);
|
||||
if (endsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenIt.hasNext()) {
|
||||
if (!Utils.isWhitespaceString(childrenIt.next())) {
|
||||
writer.write("\n");
|
||||
}
|
||||
childrenIt.previous();
|
||||
}
|
||||
} else if (item instanceof CommentNode) {
|
||||
final String content = ((CommentNode) item).getCommentedContent().trim();
|
||||
writer.write(content);
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken) item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* Properties defining cleaner's behaviour
|
||||
*/
|
||||
public class CleanerProperties {
|
||||
public static final String BOOL_ATT_SELF = "self";
|
||||
public static final String BOOL_ATT_EMPTY = "empty";
|
||||
public static final String BOOL_ATT_TRUE = "true";
|
||||
|
||||
private ITagInfoProvider tagInfoProvider = null;
|
||||
private boolean advancedXmlEscape = true;
|
||||
private boolean transResCharsToNCR = false;
|
||||
private boolean useCdataForScriptAndStyle = true;
|
||||
private boolean translateSpecialEntities = true;
|
||||
private boolean transSpecialEntitiesToNCR = false;
|
||||
private boolean recognizeUnicodeChars = true;
|
||||
private boolean omitUnknownTags = false;
|
||||
private boolean treatUnknownTagsAsContent = false;
|
||||
private boolean omitDeprecatedTags = false;
|
||||
private boolean treatDeprecatedTagsAsContent = false;
|
||||
private boolean omitComments = false;
|
||||
private boolean omitXmlDeclaration = false;
|
||||
private boolean omitDoctypeDeclaration = true;
|
||||
private boolean omitHtmlEnvelope = false;
|
||||
private boolean useEmptyElementTags = true;
|
||||
private boolean allowMultiWordAttributes = true;
|
||||
private boolean allowHtmlInsideAttributes = false;
|
||||
private boolean ignoreQuestAndExclam = true;
|
||||
private boolean namespacesAware = true;
|
||||
private String hyphenReplacementInComment = "=";
|
||||
private String booleanAttributeValues = BOOL_ATT_SELF;
|
||||
private String pruneTags = null;
|
||||
|
||||
public String getBooleanAttributeValues() {
|
||||
return booleanAttributeValues;
|
||||
}
|
||||
|
||||
public String getHyphenReplacementInComment() {
|
||||
return hyphenReplacementInComment;
|
||||
}
|
||||
|
||||
public String getPruneTags() {
|
||||
return pruneTags;
|
||||
}
|
||||
|
||||
public ITagInfoProvider getTagInfoProvider() {
|
||||
return tagInfoProvider;
|
||||
}
|
||||
|
||||
public boolean isAdvancedXmlEscape() {
|
||||
return advancedXmlEscape;
|
||||
}
|
||||
|
||||
public boolean isAllowHtmlInsideAttributes() {
|
||||
return allowHtmlInsideAttributes;
|
||||
}
|
||||
|
||||
public boolean isAllowMultiWordAttributes() {
|
||||
return allowMultiWordAttributes;
|
||||
}
|
||||
|
||||
public boolean isIgnoreQuestAndExclam() {
|
||||
return ignoreQuestAndExclam;
|
||||
}
|
||||
|
||||
public boolean isNamespacesAware() {
|
||||
return namespacesAware;
|
||||
}
|
||||
|
||||
public boolean isOmitComments() {
|
||||
return omitComments;
|
||||
}
|
||||
|
||||
public boolean isOmitDeprecatedTags() {
|
||||
return omitDeprecatedTags;
|
||||
}
|
||||
|
||||
public boolean isOmitDoctypeDeclaration() {
|
||||
return omitDoctypeDeclaration;
|
||||
}
|
||||
|
||||
public boolean isOmitHtmlEnvelope() {
|
||||
return omitHtmlEnvelope;
|
||||
}
|
||||
|
||||
public boolean isOmitUnknownTags() {
|
||||
return omitUnknownTags;
|
||||
}
|
||||
|
||||
public boolean isOmitXmlDeclaration() {
|
||||
return omitXmlDeclaration;
|
||||
}
|
||||
|
||||
public boolean isRecognizeUnicodeChars() {
|
||||
return recognizeUnicodeChars;
|
||||
}
|
||||
|
||||
public boolean isTranslateSpecialEntities() {
|
||||
return translateSpecialEntities;
|
||||
}
|
||||
|
||||
public boolean isTransResCharsToNCR() {
|
||||
return transResCharsToNCR;
|
||||
}
|
||||
|
||||
public boolean isTransSpecialEntitiesToNCR() {
|
||||
return transSpecialEntitiesToNCR;
|
||||
}
|
||||
|
||||
public boolean isTreatDeprecatedTagsAsContent() {
|
||||
return treatDeprecatedTagsAsContent;
|
||||
}
|
||||
|
||||
public boolean isTreatUnknownTagsAsContent() {
|
||||
return treatUnknownTagsAsContent;
|
||||
}
|
||||
|
||||
public boolean isUseCdataForScriptAndStyle() {
|
||||
return useCdataForScriptAndStyle;
|
||||
}
|
||||
|
||||
public boolean isUseEmptyElementTags() {
|
||||
return useEmptyElementTags;
|
||||
}
|
||||
|
||||
public void setAdvancedXmlEscape(final boolean advancedXmlEscape) {
|
||||
this.advancedXmlEscape = advancedXmlEscape;
|
||||
}
|
||||
|
||||
public void setAllowHtmlInsideAttributes(final boolean allowHtmlInsideAttributes) {
|
||||
this.allowHtmlInsideAttributes = allowHtmlInsideAttributes;
|
||||
}
|
||||
|
||||
public void setAllowMultiWordAttributes(final boolean allowMultiWordAttributes) {
|
||||
this.allowMultiWordAttributes = allowMultiWordAttributes;
|
||||
}
|
||||
|
||||
public void setBooleanAttributeValues(final String booleanAttributeValues) {
|
||||
if (BOOL_ATT_SELF.equalsIgnoreCase(booleanAttributeValues)
|
||||
|| BOOL_ATT_EMPTY.equalsIgnoreCase(booleanAttributeValues)
|
||||
|| BOOL_ATT_TRUE.equalsIgnoreCase(booleanAttributeValues)) {
|
||||
this.booleanAttributeValues = booleanAttributeValues.toLowerCase();
|
||||
} else {
|
||||
this.booleanAttributeValues = BOOL_ATT_SELF;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHyphenReplacementInComment(final String hyphenReplacementInComment) {
|
||||
this.hyphenReplacementInComment = hyphenReplacementInComment;
|
||||
}
|
||||
|
||||
public void setIgnoreQuestAndExclam(final boolean ignoreQuestAndExclam) {
|
||||
this.ignoreQuestAndExclam = ignoreQuestAndExclam;
|
||||
}
|
||||
|
||||
public void setNamespacesAware(final boolean namespacesAware) {
|
||||
this.namespacesAware = namespacesAware;
|
||||
}
|
||||
|
||||
public void setOmitComments(final boolean omitComments) {
|
||||
this.omitComments = omitComments;
|
||||
}
|
||||
|
||||
public void setOmitDeprecatedTags(final boolean omitDeprecatedTags) {
|
||||
this.omitDeprecatedTags = omitDeprecatedTags;
|
||||
}
|
||||
|
||||
public void setOmitDoctypeDeclaration(final boolean omitDoctypeDeclaration) {
|
||||
this.omitDoctypeDeclaration = omitDoctypeDeclaration;
|
||||
}
|
||||
|
||||
public void setOmitHtmlEnvelope(final boolean omitHtmlEnvelope) {
|
||||
this.omitHtmlEnvelope = omitHtmlEnvelope;
|
||||
}
|
||||
|
||||
public void setOmitUnknownTags(final boolean omitUnknownTags) {
|
||||
this.omitUnknownTags = omitUnknownTags;
|
||||
}
|
||||
|
||||
public void setOmitXmlDeclaration(final boolean omitXmlDeclaration) {
|
||||
this.omitXmlDeclaration = omitXmlDeclaration;
|
||||
}
|
||||
|
||||
public void setPruneTags(final String pruneTags) {
|
||||
this.pruneTags = pruneTags;
|
||||
}
|
||||
|
||||
public void setRecognizeUnicodeChars(final boolean recognizeUnicodeChars) {
|
||||
this.recognizeUnicodeChars = recognizeUnicodeChars;
|
||||
}
|
||||
|
||||
public void setTagInfoProvider(final ITagInfoProvider tagInfoProvider) {
|
||||
this.tagInfoProvider = tagInfoProvider;
|
||||
}
|
||||
|
||||
public void setTranslateSpecialEntities(final boolean translateSpecialEntities) {
|
||||
this.translateSpecialEntities = translateSpecialEntities;
|
||||
}
|
||||
|
||||
public void setTransResCharsToNCR(final boolean transResCharsToNCR) {
|
||||
this.transResCharsToNCR = transResCharsToNCR;
|
||||
}
|
||||
|
||||
public void setTransSpecialEntitiesToNCR(final boolean transSpecialEntitiesToNCR) {
|
||||
this.transSpecialEntitiesToNCR = transSpecialEntitiesToNCR;
|
||||
}
|
||||
|
||||
public void setTreatDeprecatedTagsAsContent(final boolean treatDeprecatedTagsAsContent) {
|
||||
this.treatDeprecatedTagsAsContent = treatDeprecatedTagsAsContent;
|
||||
}
|
||||
|
||||
public void setTreatUnknownTagsAsContent(final boolean treatUnknownTagsAsContent) {
|
||||
this.treatUnknownTagsAsContent = treatUnknownTagsAsContent;
|
||||
}
|
||||
|
||||
public void setUseCdataForScriptAndStyle(final boolean useCdataForScriptAndStyle) {
|
||||
this.useCdataForScriptAndStyle = useCdataForScriptAndStyle;
|
||||
}
|
||||
|
||||
public void setUseEmptyElementTags(final boolean useEmptyElementTags) {
|
||||
this.useEmptyElementTags = useEmptyElementTags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Contains transformation collection.
|
||||
*/
|
||||
public class CleanerTransformations {
|
||||
|
||||
final private Map<String, TagTransformation> mappings = new HashMap<String, TagTransformation>();
|
||||
|
||||
/**
|
||||
* Adds specified tag transformation to the collection.
|
||||
*
|
||||
* @param tagTransformation
|
||||
*/
|
||||
public void addTransformation(final TagTransformation tagTransformation) {
|
||||
if (tagTransformation != null) {
|
||||
mappings.put(tagTransformation.getSourceTag(), tagTransformation);
|
||||
}
|
||||
}
|
||||
|
||||
public TagTransformation getTransformation(final String tagName) {
|
||||
return tagName == null ? null : (TagTransformation) mappings.get(tagName.toLowerCase());
|
||||
}
|
||||
|
||||
public boolean hasTransformationForTag(final String tagName) {
|
||||
return tagName != null && mappings.containsKey(tagName.toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* HTML comment token.
|
||||
* </p>
|
||||
*/
|
||||
public class CommentNode implements BaseToken, HtmlNode {
|
||||
final private StringBuilder content;
|
||||
|
||||
public CommentNode(final String content) {
|
||||
this.content = new StringBuilder(content);
|
||||
}
|
||||
|
||||
public String getCommentedContent() {
|
||||
return "<!--" + content + "-->";
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void serialize(final Serializer serializer, final Writer writer) throws IOException {
|
||||
writer.write(getCommentedContent());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getCommentedContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Compact HTML serializer - creates resulting HTML by stripping whitespaces
|
||||
* wherever possible.
|
||||
* </p>
|
||||
*/
|
||||
public class CompactHtmlSerializer extends HtmlSerializer {
|
||||
private int openPreTags = 0;
|
||||
|
||||
public CompactHtmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
final boolean isPreTag = "pre".equalsIgnoreCase(tagNode.getName());
|
||||
if (isPreTag) {
|
||||
openPreTags++;
|
||||
}
|
||||
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
final List<Object> tagChildren = tagNode.getChildren();
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
final ListIterator<Object> childrenIt = tagChildren.listIterator();
|
||||
while (childrenIt.hasNext()) {
|
||||
final Object item = childrenIt.next();
|
||||
if (item instanceof ContentNode) {
|
||||
String content = item.toString();
|
||||
if (openPreTags > 0) {
|
||||
writer.write(content);
|
||||
} else {
|
||||
final boolean startsWithSpace = content.length() > 0
|
||||
&& Character.isWhitespace(content.charAt(0));
|
||||
final boolean endsWithSpace = content.length() > 1
|
||||
&& Character.isWhitespace(content.charAt(content.length() - 1));
|
||||
content = dontEscape(tagNode) ? content.trim() : escapeText(content.trim());
|
||||
|
||||
if (startsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
|
||||
if (content.length() != 0) {
|
||||
writer.write(content);
|
||||
if (endsWithSpace) {
|
||||
writer.write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenIt.hasNext()) {
|
||||
if (!Utils.isWhitespaceString(childrenIt.next())) {
|
||||
writer.write("\n");
|
||||
}
|
||||
childrenIt.previous();
|
||||
}
|
||||
}
|
||||
} else if (item instanceof CommentNode) {
|
||||
final String content = ((CommentNode) item).getCommentedContent().trim();
|
||||
writer.write(content);
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken) item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
if (isPreTag) {
|
||||
openPreTags--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Compact XML serializer - creates resulting XML by stripping whitespaces.
|
||||
* </p>
|
||||
*/
|
||||
public class CompactXmlSerializer extends XmlSerializer {
|
||||
public CompactXmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
final List<Object> tagChildren = tagNode.getChildren();
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
final ListIterator<Object> childrenIt = tagChildren.listIterator();
|
||||
while (childrenIt.hasNext()) {
|
||||
final Object item = childrenIt.next();
|
||||
if (item instanceof ContentNode) {
|
||||
final String content = item.toString().trim();
|
||||
writer.write(dontEscape(tagNode) ? content.replaceAll("]]>", "]]>") : escapeXml(content));
|
||||
|
||||
if (childrenIt.hasNext()) {
|
||||
if (!Utils.isWhitespaceString(childrenIt.next())) {
|
||||
writer.write("\n");
|
||||
}
|
||||
childrenIt.previous();
|
||||
}
|
||||
} else if (item instanceof CommentNode) {
|
||||
final String content = ((CommentNode) item).getCommentedContent().trim();
|
||||
writer.write(content);
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken) item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* HTML text token.
|
||||
* </p>
|
||||
*/
|
||||
public class ContentNode implements BaseToken, HtmlNode {
|
||||
final private StringBuilder content;
|
||||
|
||||
public ContentNode(final char content[], final int len) {
|
||||
this.content = new StringBuilder(len + 16);
|
||||
this.content.append(content, 0, len);
|
||||
}
|
||||
|
||||
public ContentNode(final String content) {
|
||||
this.content = new StringBuilder(content);
|
||||
}
|
||||
|
||||
public StringBuilder getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void serialize(final Serializer serializer, final Writer writer) throws IOException {
|
||||
writer.write(content.toString());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class is automatically created from ConfigFileTagProvider which reads
|
||||
* default XML configuration file with tag descriptions. It is used as default
|
||||
* tag info provider. Class is created for performance purposes - parsing XML
|
||||
* file requires some processing time.
|
||||
*/
|
||||
public class DefaultTagProvider extends HashMap<String, TagInfo> implements ITagInfoProvider {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// singleton instance, used if no other TagInfoProvider is specified
|
||||
private static DefaultTagProvider instance;
|
||||
|
||||
/**
|
||||
* @return Singleton instance of this class.
|
||||
*/
|
||||
public static synchronized DefaultTagProvider getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DefaultTagProvider();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public DefaultTagProvider() {
|
||||
super();
|
||||
|
||||
TagInfo tagInfo;
|
||||
|
||||
tagInfo = new TagInfo("div", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("div", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("span", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("span", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("meta", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("meta", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("link", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("link", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("title", TagInfo.CONTENT_TEXT, TagInfo.HEAD, false, true, false);
|
||||
this.put("title", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("style", TagInfo.CONTENT_TEXT, TagInfo.HEAD, false, false, false);
|
||||
this.put("style", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("bgsound", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("bgsound", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h1", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h1", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h2", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h2", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h3", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h3", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h4", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h4", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h5", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h5", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("h6", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("h1,h2,h3,h4,h5,h6,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("h6", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("p", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("p", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("strong", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("strong", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("em", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("em", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("abbr", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("abbr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("acronym", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("acronym", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("address", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("address", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("bdo", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("bdo", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("blockquote", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("blockquote", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("cite", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("cite", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("q", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("q", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("code", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("code", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("ins", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("ins", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("del", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("del", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dfn", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("dfn", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("kbd", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("kbd", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("pre", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("pre", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("samp", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("samp", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("listing", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("listing", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("var", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("var", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("br", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
this.put("br", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("wbr", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
this.put("wbr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("nobr", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("nobr");
|
||||
this.put("nobr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("xmp", TagInfo.CONTENT_TEXT, TagInfo.BODY, false, false, false);
|
||||
this.put("xmp", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("a", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("a");
|
||||
this.put("a", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("base", TagInfo.CONTENT_NONE, TagInfo.HEAD, false, false, false);
|
||||
this.put("base", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("img", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
this.put("img", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("area", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("map");
|
||||
tagInfo.defineCloseBeforeTags("area");
|
||||
this.put("area", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("map", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("map");
|
||||
this.put("map", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("object", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("object", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("param", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("param", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("applet", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
this.put("applet", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("xml", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("xml", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("ul", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("ul", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("ol", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("ol", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("li", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("li,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("li", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dl", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("dl", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dt", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("dt,dd");
|
||||
this.put("dt", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dd", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("dt,dd");
|
||||
this.put("dd", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("menu", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("menu", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("dir", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("dir", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("table", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineAllowedChildrenTags("tr,tbody,thead,tfoot,colgroup,col,form,caption,tr");
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("tr,thead,tbody,tfoot,caption,colgroup,table,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param");
|
||||
this.put("table", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tr", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineRequiredEnclosingTags("tbody");
|
||||
tagInfo.defineAllowedChildrenTags("td,th");
|
||||
tagInfo.defineHigherLevelTags("thead,tfoot");
|
||||
tagInfo.defineCloseBeforeTags("tr,td,th,caption,colgroup");
|
||||
this.put("tr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("td", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineRequiredEnclosingTags("tr");
|
||||
tagInfo.defineCloseBeforeTags("td,th,caption,colgroup");
|
||||
this.put("td", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("th", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineRequiredEnclosingTags("tr");
|
||||
tagInfo.defineCloseBeforeTags("td,th,caption,colgroup");
|
||||
this.put("th", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tbody", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("tr,form");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("tbody", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("thead", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("tr,form");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("thead", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tfoot", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("tr,form");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("tfoot", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("col", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
this.put("col", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("colgroup", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineAllowedChildrenTags("col");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("colgroup", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("caption", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineFatalTags("table");
|
||||
tagInfo.defineCloseBeforeTags("td,th,tr,tbody,thead,tfoot,caption,colgroup");
|
||||
this.put("caption", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("form", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineForbiddenTags("form");
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo
|
||||
.defineCloseBeforeTags("option,optgroup,textarea,select,fieldset,p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("form", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("input", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("select,optgroup,option");
|
||||
this.put("input", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("textarea", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("select,optgroup,option");
|
||||
this.put("textarea", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("select", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineAllowedChildrenTags("option,optgroup");
|
||||
tagInfo.defineCloseBeforeTags("option,optgroup,select");
|
||||
this.put("select", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("option", TagInfo.CONTENT_TEXT, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineFatalTags("select");
|
||||
tagInfo.defineCloseBeforeTags("option");
|
||||
this.put("option", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("optgroup", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, true);
|
||||
tagInfo.defineFatalTags("select");
|
||||
tagInfo.defineAllowedChildrenTags("option");
|
||||
tagInfo.defineCloseBeforeTags("optgroup");
|
||||
this.put("optgroup", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("button", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeTags("select,optgroup,option");
|
||||
this.put("button", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("label", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("label", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("fieldset", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("fieldset", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("legend", TagInfo.CONTENT_TEXT, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineRequiredEnclosingTags("fieldset");
|
||||
tagInfo.defineCloseBeforeTags("legend");
|
||||
this.put("legend", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("isindex", TagInfo.CONTENT_NONE, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("isindex", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("script", TagInfo.CONTENT_ALL, TagInfo.HEAD_AND_BODY, false, false, false);
|
||||
this.put("script", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("noscript", TagInfo.CONTENT_ALL, TagInfo.HEAD_AND_BODY, false, false, false);
|
||||
this.put("noscript", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("b", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("u,i,tt,sub,sup,big,small,strike,blink,s");
|
||||
this.put("b", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("i", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,tt,sub,sup,big,small,strike,blink,s");
|
||||
this.put("i", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("u", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,i,tt,sub,sup,big,small,strike,blink,s");
|
||||
this.put("u", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("tt", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,sub,sup,big,small,strike,blink,s");
|
||||
this.put("tt", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("sub", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sup,big,small,strike,blink,s");
|
||||
this.put("sub", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("sup", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,big,small,strike,blink,s");
|
||||
this.put("sup", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("big", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,small,strike,blink,s");
|
||||
this.put("big", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("small", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,strike,blink,s");
|
||||
this.put("small", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("strike", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,small,blink,s");
|
||||
this.put("strike", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("blink", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,small,strike,s");
|
||||
this.put("blink", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("marquee", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("marquee", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("s", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseInsideCopyAfterTags("b,u,i,tt,sub,sup,big,small,strike,blink");
|
||||
this.put("s", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("hr", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("hr", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("font", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
this.put("font", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("basefont", TagInfo.CONTENT_NONE, TagInfo.BODY, true, false, false);
|
||||
this.put("basefont", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("center", TagInfo.CONTENT_ALL, TagInfo.BODY, true, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("center", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("comment", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("comment", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("server", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("server", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("iframe", TagInfo.CONTENT_ALL, TagInfo.BODY, false, false, false);
|
||||
this.put("iframe", tagInfo);
|
||||
|
||||
tagInfo = new TagInfo("embed", TagInfo.CONTENT_NONE, TagInfo.BODY, false, false, false);
|
||||
tagInfo.defineCloseBeforeCopyInsideTags("a,bdo,strong,em,q,b,i,u,tt,sub,sup,big,small,strike,s,font");
|
||||
tagInfo.defineCloseBeforeTags("p,address,label,abbr,acronym,dfn,kbd,samp,var,cite,code,param,xml");
|
||||
this.put("embed", tagInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new tag info.
|
||||
*
|
||||
* @param tagInfo
|
||||
* tag info to be added to the provider.
|
||||
*/
|
||||
public void addTagInfo(TagInfo tagInfo) {
|
||||
if (tagInfo != null) {
|
||||
put(tagInfo.getName().toLowerCase(), tagInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public TagInfo getTagInfo(String tagName) {
|
||||
return get(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes tag info with specified name.
|
||||
*
|
||||
* @param tagName
|
||||
* Name of the tag to be removed from the tag provider.
|
||||
*/
|
||||
public void removeTagInfo(String tagName) {
|
||||
if (tagName != null) {
|
||||
remove(tagName.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* HTML doctype token.
|
||||
* </p>
|
||||
*/
|
||||
public class DoctypeToken implements BaseToken {
|
||||
private static String clean(String s) {
|
||||
if (s != null) {
|
||||
s = s.replace('>', ' ');
|
||||
s = s.replace('<', ' ');
|
||||
s = s.replace('&', ' ');
|
||||
s = s.replace('\'', ' ');
|
||||
s = s.replace('\"', ' ');
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
final private String part1;
|
||||
final private String part2;
|
||||
final private String part3;
|
||||
final private String part4;
|
||||
|
||||
public DoctypeToken(final String part1, final String part2, final String part3, final String part4) {
|
||||
this.part1 = part1 != null ? part1.toUpperCase() : part1;
|
||||
this.part2 = part2 != null ? part2.toUpperCase() : part2;
|
||||
this.part3 = clean(part3);
|
||||
this.part4 = clean(part4);
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
String result = "<!DOCTYPE " + part1 + " ";
|
||||
result += part2 + " \"" + part3 + "\"";
|
||||
if (part4 != null && !"".equals(part4)) {
|
||||
result += " \"" + part4 + "\"";
|
||||
}
|
||||
result += ">";
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getPart1() {
|
||||
return part1;
|
||||
}
|
||||
|
||||
public String getPart2() {
|
||||
return part2;
|
||||
}
|
||||
|
||||
public String getPart3() {
|
||||
return part3;
|
||||
}
|
||||
|
||||
public String getPart4() {
|
||||
return part4;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
if (part1 == null || "".equals(part1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!"public".equalsIgnoreCase(part2) && !"system".equalsIgnoreCase(part2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("system".equalsIgnoreCase(part2) && part4 != null && !"".equals(part4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("public".equalsIgnoreCase(part2) && (part4 == null || "".equals(part4))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void serialize(final Serializer serializer, final Writer writer) throws IOException {
|
||||
writer.write(getContent() + "\n");
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import org.w3c.dom.Comment;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* DOM serializer - creates xml DOM.
|
||||
* </p>
|
||||
*/
|
||||
public class DomSerializer {
|
||||
protected CleanerProperties props;
|
||||
protected boolean escapeXml = true;
|
||||
|
||||
public DomSerializer(final CleanerProperties props, final boolean escapeXml) {
|
||||
this.props = props;
|
||||
this.escapeXml = escapeXml;
|
||||
}
|
||||
|
||||
public DomSerializer(final CleanerProperties props) {
|
||||
this(props, true);
|
||||
}
|
||||
|
||||
public Document createDOM(final TagNode rootNode) throws ParserConfigurationException {
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
final Document document = factory.newDocumentBuilder().newDocument();
|
||||
final Element rootElement = createElement(rootNode, document);
|
||||
document.appendChild(rootElement);
|
||||
setAttributes(rootNode, rootElement);
|
||||
createSubnodes(document, rootElement, rootNode.getChildren());
|
||||
return document;
|
||||
}
|
||||
|
||||
private Element createElement(final TagNode node, final Document document) {
|
||||
String name = node.getName();
|
||||
final boolean nsAware = props.isNamespacesAware();
|
||||
final String prefix = Utils.getXmlNSPrefix(name);
|
||||
final Map<String, String> nsDeclarations = node.getNamespaceDeclarations();
|
||||
String nsURI = null;
|
||||
if (prefix != null) {
|
||||
if (nsAware) {
|
||||
if (nsDeclarations != null) {
|
||||
nsURI = nsDeclarations.get(prefix);
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = node.getNamespaceURIOnPath(prefix);
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = prefix;
|
||||
}
|
||||
} else {
|
||||
name = Utils.getXmlName(name);
|
||||
}
|
||||
} else {
|
||||
if (nsAware) {
|
||||
if (nsDeclarations != null) {
|
||||
nsURI = nsDeclarations.get("");
|
||||
}
|
||||
if (nsURI == null) {
|
||||
nsURI = node.getNamespaceURIOnPath(prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nsAware && nsURI != null) {
|
||||
return document.createElementNS(nsURI, name);
|
||||
} else {
|
||||
return document.createElement(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAttributes(final TagNode node, final Element element) {
|
||||
for (final Map.Entry<String, String> entry : node.getAttributes().entrySet()) {
|
||||
final String attrName = entry.getKey();
|
||||
String attrValue = entry.getValue();
|
||||
if (escapeXml) {
|
||||
attrValue = Utils.escapeXml(attrValue, props, true);
|
||||
}
|
||||
|
||||
final String attPrefix = Utils.getXmlNSPrefix(attrName);
|
||||
if (attPrefix != null) {
|
||||
if (props.isNamespacesAware()) {
|
||||
String nsURI = node.getNamespaceURIOnPath(attPrefix);
|
||||
if (nsURI == null) {
|
||||
nsURI = attPrefix;
|
||||
}
|
||||
element.setAttributeNS(nsURI, attrName, attrValue);
|
||||
} else {
|
||||
element.setAttribute(Utils.getXmlName(attrName), attrValue);
|
||||
}
|
||||
} else {
|
||||
element.setAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createSubnodes(final Document document, final Element element, final List<Object> tagChildren) {
|
||||
if (tagChildren != null) {
|
||||
final Iterator<Object> it = tagChildren.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Object item = it.next();
|
||||
if (item instanceof CommentNode) {
|
||||
final CommentNode commentNode = (CommentNode) item;
|
||||
final Comment comment = document.createComment(commentNode.getContent().toString());
|
||||
element.appendChild(comment);
|
||||
} else if (item instanceof ContentNode) {
|
||||
final String nodeName = element.getNodeName();
|
||||
String content = item.toString();
|
||||
final boolean specialCase = props.isUseCdataForScriptAndStyle()
|
||||
&& ("script".equalsIgnoreCase(nodeName) || "style".equalsIgnoreCase(nodeName));
|
||||
if (escapeXml && !specialCase) {
|
||||
content = Utils.escapeXml(content, props, true);
|
||||
}
|
||||
element.appendChild(specialCase ? document.createCDATASection(content) : document
|
||||
.createTextNode(content));
|
||||
} else if (item instanceof TagNode) {
|
||||
final TagNode subTagNode = (TagNode) item;
|
||||
final Element subelement = createElement(subTagNode, document);
|
||||
|
||||
setAttributes(subTagNode, subelement);
|
||||
|
||||
// recursively create subnodes
|
||||
createSubnodes(document, subelement, subTagNode.getChildren());
|
||||
|
||||
element.appendChild(subelement);
|
||||
} else if (item instanceof List) {
|
||||
final List sublist = (List) item;
|
||||
createSubnodes(document, element, sublist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* HTML tag end token.
|
||||
* </p>
|
||||
*/
|
||||
public class EndTagToken extends TagToken {
|
||||
public EndTagToken() {
|
||||
super();
|
||||
}
|
||||
|
||||
public EndTagToken(final String name) {
|
||||
super(name == null ? null : name.toLowerCase());
|
||||
}
|
||||
|
||||
public void setAttribute(final String attName, final String attValue) {
|
||||
// do nothing - simply ignore attributes in closing tag
|
||||
}
|
||||
|
||||
public void serialize(final Serializer serializer, final Writer writer) {
|
||||
// do nothing - simply ignore serialization
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class FastHtmlSerializer extends HtmlSerializer {
|
||||
public FastHtmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
final Stack<TagNode> tagStack = new Stack<TagNode>();
|
||||
final Stack<List<Object>> childStack = new Stack<List<Object>>();
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
tagStack.push(tagNode);
|
||||
childStack.push(new ArrayList<Object>(tagNode.getChildren()));
|
||||
while (!tagStack.isEmpty()) {
|
||||
final TagNode tag = tagStack.peek();
|
||||
final List<Object> children = childStack.peek();
|
||||
if (children.isEmpty()) {
|
||||
tagStack.pop();
|
||||
childStack.pop();
|
||||
if (!isMinimizedTagSyntax(tag)) {
|
||||
serializeEndTag(tag, writer, false);
|
||||
}
|
||||
} else {
|
||||
final Object item = children.get(0);
|
||||
children.remove(0);
|
||||
if (item instanceof ContentNode) {
|
||||
final String content = item.toString();
|
||||
writer.write(dontEscape(tag) ? content : escapeText(content));
|
||||
} else if (item instanceof TagNode) {
|
||||
final TagNode currentTag = (TagNode) item;
|
||||
serializeOpenTag(currentTag, writer, false);
|
||||
tagStack.push(currentTag);
|
||||
childStack.push(new ArrayList<Object>(currentTag.getChildren()));
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken) item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,932 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Main HtmlCleaner class.
|
||||
*
|
||||
* <p>
|
||||
* It represents public interface to the user. It's task is to call tokenizer
|
||||
* with specified source HTML, traverse list of produced token list and create
|
||||
* internal object model. It also offers a set of methods to write resulting XML
|
||||
* to string, file or any output stream.
|
||||
* </p>
|
||||
* <p>
|
||||
* Typical usage is the following:
|
||||
* </p>
|
||||
*
|
||||
* <xmp> // create an instance of HtmlCleaner HtmlCleaner cleaner = new
|
||||
* HtmlCleaner();
|
||||
*
|
||||
* // take default cleaner properties CleanerProperties props =
|
||||
* cleaner.getProperties();
|
||||
*
|
||||
* // customize cleaner's behaviour with property setters props.setXXX(...);
|
||||
*
|
||||
* // Clean HTML taken from simple string, file, URL, input stream, // input
|
||||
* source or reader. Result is root node of created // tree-like structure.
|
||||
* Single cleaner instance may be safely used // multiple times. TagNode node =
|
||||
* cleaner.clean(...);
|
||||
*
|
||||
* // optionally find parts of the DOM or modify some nodes TagNode[] myNodes =
|
||||
* node.getElementsByXXX(...); // and/or Object[] myNodes =
|
||||
* node.evaluateXPath(xPathExpression); // and/or aNode.removeFromTree(); //
|
||||
* and/or aNode.addAttribute(attName, attValue); // and/or
|
||||
* aNode.removeAttribute(attName, attValue); // and/or
|
||||
* cleaner.setInnerHtml(aNode, htmlContent); // and/or do some other tree
|
||||
* manipulation/traversal
|
||||
*
|
||||
* // serialize a node to a file, output stream, DOM, JDom... new
|
||||
* XXXSerializer(props).writeXmlXXX(aNode, ...); myJDom = new
|
||||
* JDomSerializer(props, true).createJDom(aNode); myDom = new
|
||||
* DomSerializer(props, true).createDOM(aNode); </xmp>
|
||||
*/
|
||||
public class HtmlCleaner {
|
||||
|
||||
private class CleanTimeValues {
|
||||
private OpenTags openTags;
|
||||
private boolean headOpened = false;
|
||||
private boolean bodyOpened = false;
|
||||
private Set headTags = new LinkedHashSet();
|
||||
private Set allTags = new TreeSet();
|
||||
|
||||
private TagNode htmlNode;
|
||||
private TagNode bodyNode;
|
||||
private TagNode headNode;
|
||||
private TagNode rootNode;
|
||||
|
||||
private Set<String> pruneTagSet = new HashSet<String>();
|
||||
private Set<TagNode> pruneNodeSet = new HashSet<TagNode>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that contains information and mathods for managing list of open,
|
||||
* but unhandled tags.
|
||||
*/
|
||||
private class OpenTags {
|
||||
private List<TagPos> list = new ArrayList<TagPos>();
|
||||
private TagPos last = null;
|
||||
private Set<String> set = new HashSet<String>();
|
||||
|
||||
private void addTag(String tagName, int position) {
|
||||
last = new TagPos(position, tagName);
|
||||
list.add(last);
|
||||
set.add(tagName);
|
||||
}
|
||||
|
||||
private TagPos findFirstTagPos() {
|
||||
return list.isEmpty() ? null : list.get(0);
|
||||
}
|
||||
|
||||
private TagPos findTag(String tagName) {
|
||||
if (tagName != null) {
|
||||
final ListIterator<TagPos> it = list.listIterator(list.size());
|
||||
String fatalTag = null;
|
||||
final TagInfo fatalInfo = tagInfoProvider.getTagInfo(tagName);
|
||||
if (fatalInfo != null) {
|
||||
fatalTag = fatalInfo.getFatalTag();
|
||||
}
|
||||
|
||||
while (it.hasPrevious()) {
|
||||
final TagPos currTagPos = it.previous();
|
||||
if (tagName.equals(currTagPos.name)) {
|
||||
return currTagPos;
|
||||
} else if (fatalTag != null && fatalTag.equals(currTagPos.name)) {
|
||||
// do not search past a fatal tag for this tag
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private TagPos findTagToPlaceRubbish() {
|
||||
TagPos result = null, prev = null;
|
||||
|
||||
if (!isEmpty()) {
|
||||
final ListIterator<TagPos> it = list.listIterator(list.size());
|
||||
while (it.hasPrevious()) {
|
||||
result = it.previous();
|
||||
if (result.info == null || result.info.allowsAnything()) {
|
||||
if (prev != null) {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
prev = result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TagPos getLastTagPos() {
|
||||
return last;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
private void removeTag(final String tagName) {
|
||||
final ListIterator<TagPos> it = list.listIterator(list.size());
|
||||
while (it.hasPrevious()) {
|
||||
final TagPos currTagPos = it.previous();
|
||||
if (tagName.equals(currTagPos.name)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
last = list.isEmpty() ? null : list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any of tags specified in the set are already open.
|
||||
*
|
||||
* @param tags
|
||||
*/
|
||||
private boolean someAlreadyOpen(final Set tags) {
|
||||
final Iterator<TagPos> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
final TagPos curr = it.next();
|
||||
if (tags.contains(curr.name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean tagEncountered(final String tagName) {
|
||||
return set.contains(tagName);
|
||||
}
|
||||
|
||||
private boolean tagExists(final String tagName) {
|
||||
return findTag(tagName) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains information about single open tag
|
||||
*/
|
||||
private class TagPos {
|
||||
private int position;
|
||||
private String name;
|
||||
private TagInfo info;
|
||||
|
||||
TagPos(final int position, final String name) {
|
||||
this.position = position;
|
||||
this.name = name;
|
||||
this.info = tagInfoProvider.getTagInfo(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static final String DEFAULT_CHARSET = System.getProperty("file.encoding");
|
||||
private CleanerProperties properties;
|
||||
private ITagInfoProvider tagInfoProvider;
|
||||
private CleanerTransformations transformations = null;
|
||||
|
||||
/**
|
||||
* Constructor - creates cleaner instance with default tag info provider and
|
||||
* default properties.
|
||||
*/
|
||||
public HtmlCleaner() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - creates the instance with default tag info provider and
|
||||
* specified properties
|
||||
*
|
||||
* @param properties
|
||||
* Properties used during parsing and serializing
|
||||
*/
|
||||
public HtmlCleaner(final CleanerProperties properties) {
|
||||
this(null, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - creates the instance with specified tag info provider and
|
||||
* default properties
|
||||
*
|
||||
* @param tagInfoProvider
|
||||
* Provider for tag filtering and balancing
|
||||
*/
|
||||
public HtmlCleaner(final ITagInfoProvider tagInfoProvider) {
|
||||
this(tagInfoProvider, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - creates the instance with specified tag info provider and
|
||||
* specified properties
|
||||
*
|
||||
* @param tagInfoProvider
|
||||
* Provider for tag filtering and balancing
|
||||
* @param properties
|
||||
* Properties used during parsing and serializing
|
||||
*/
|
||||
public HtmlCleaner(final ITagInfoProvider tagInfoProvider, final CleanerProperties properties) {
|
||||
this.tagInfoProvider = (tagInfoProvider == null) ? DefaultTagProvider.getInstance() : tagInfoProvider;
|
||||
this.properties = properties == null ? new CleanerProperties() : properties;
|
||||
this.properties.setTagInfoProvider(this.tagInfoProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add attributes from specified map to the specified tag. If some attribute
|
||||
* already exist it is preserved.
|
||||
*
|
||||
* @param tag
|
||||
* @param attributes
|
||||
*/
|
||||
private void addAttributesToTag(final TagNode tag, final Map attributes) {
|
||||
if (attributes != null) {
|
||||
final Map tagAttributes = tag.getAttributes();
|
||||
final Iterator it = attributes.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
final Map.Entry currEntry = (Map.Entry) it.next();
|
||||
final String attName = (String) currEntry.getKey();
|
||||
if (!tagAttributes.containsKey(attName)) {
|
||||
final String attValue = (String) currEntry.getValue();
|
||||
tag.setAttribute(attName, attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified tag with specified info is candidate for moving to
|
||||
* head section.
|
||||
*
|
||||
* @param tagInfo
|
||||
* @param tagNode
|
||||
*/
|
||||
private void addPossibleHeadCandidate(final TagInfo tagInfo, final TagNode tagNode,
|
||||
final CleanTimeValues cleanTimeValues) {
|
||||
if (tagInfo != null && tagNode != null) {
|
||||
if (tagInfo.isHeadTag()
|
||||
|| (tagInfo.isHeadAndBodyTag() && cleanTimeValues.headOpened && !cleanTimeValues.bodyOpened)) {
|
||||
cleanTimeValues.headTags.add(tagNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns root node to internal variable. Root node of the result depends
|
||||
* on parameter "omitHtmlEnvelope". If it is set, then first child of the
|
||||
* body will be root node, or html will be root node otherwise.
|
||||
*/
|
||||
private void calculateRootNode(final CleanTimeValues cleanTimeValues) {
|
||||
cleanTimeValues.rootNode = cleanTimeValues.htmlNode;
|
||||
|
||||
if (properties.isOmitHtmlEnvelope()) {
|
||||
final List bodyChildren = cleanTimeValues.bodyNode.getChildren();
|
||||
if (bodyChildren != null) {
|
||||
for (Object child : bodyChildren) {
|
||||
// if found child that is tag itself, then return it
|
||||
if (child instanceof TagNode) {
|
||||
cleanTimeValues.rootNode = (TagNode) child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TagNode clean(final File file) throws IOException {
|
||||
return clean(file, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
public TagNode clean(final File file, final String charset) throws IOException {
|
||||
final FileInputStream in = new FileInputStream(file);
|
||||
final Reader reader = new InputStreamReader(in, charset);
|
||||
return clean(reader);
|
||||
}
|
||||
|
||||
public TagNode clean(final InputStream in) throws IOException {
|
||||
return clean(in, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
public TagNode clean(final InputStream in, final String charset) throws IOException {
|
||||
return clean(new InputStreamReader(in, charset));
|
||||
}
|
||||
|
||||
public TagNode clean(final Reader reader) throws IOException {
|
||||
return clean(reader, new CleanTimeValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic version of the cleaning call.
|
||||
*
|
||||
* @param reader
|
||||
* @return An instance of TagNode object which is the root of the XML tree.
|
||||
* @throws IOException
|
||||
*/
|
||||
public TagNode clean(final Reader reader, final CleanTimeValues cleanTimeValues) throws IOException {
|
||||
cleanTimeValues.openTags = new OpenTags();
|
||||
cleanTimeValues.headOpened = false;
|
||||
cleanTimeValues.bodyOpened = false;
|
||||
cleanTimeValues.headTags.clear();
|
||||
cleanTimeValues.allTags.clear();
|
||||
setPruneTags(properties.getPruneTags(), cleanTimeValues);
|
||||
|
||||
cleanTimeValues.htmlNode = createTagNode("html", cleanTimeValues);
|
||||
cleanTimeValues.bodyNode = createTagNode("body", cleanTimeValues);
|
||||
cleanTimeValues.headNode = createTagNode("head", cleanTimeValues);
|
||||
cleanTimeValues.rootNode = null;
|
||||
cleanTimeValues.htmlNode.addChild(cleanTimeValues.headNode);
|
||||
cleanTimeValues.htmlNode.addChild(cleanTimeValues.bodyNode);
|
||||
|
||||
final HtmlTokenizer htmlTokenizer = new HtmlTokenizer(reader, properties, transformations, tagInfoProvider) {
|
||||
@Override
|
||||
public TagNode createTagNode(final String name) {
|
||||
return HtmlCleaner.this.createTagNode(name, cleanTimeValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeTree(final List<BaseToken> tokenList) {
|
||||
HtmlCleaner.this.makeTree(tokenList, tokenList.listIterator(tokenList.size() - 1), cleanTimeValues);
|
||||
}
|
||||
};
|
||||
|
||||
htmlTokenizer.start();
|
||||
|
||||
final List<BaseToken> nodeList = htmlTokenizer.getTokenList();
|
||||
closeAll(nodeList, cleanTimeValues);
|
||||
createDocumentNodes(nodeList, cleanTimeValues);
|
||||
|
||||
calculateRootNode(cleanTimeValues);
|
||||
|
||||
// if there are some nodes to prune from tree
|
||||
if (cleanTimeValues.pruneNodeSet != null && !cleanTimeValues.pruneNodeSet.isEmpty()) {
|
||||
final Iterator iterator = cleanTimeValues.pruneNodeSet.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final TagNode tagNode = (TagNode) iterator.next();
|
||||
final TagNode parent = tagNode.getParent();
|
||||
if (parent != null) {
|
||||
parent.removeChild(tagNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanTimeValues.rootNode.setDocType(htmlTokenizer.getDocType());
|
||||
|
||||
return cleanTimeValues.rootNode;
|
||||
}
|
||||
|
||||
public TagNode clean(final String htmlContent) {
|
||||
try {
|
||||
return clean(new StringReader(htmlContent));
|
||||
} catch (final IOException e) {
|
||||
// should never happen because reading from StringReader
|
||||
throw new HtmlCleanerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instance from the content downloaded from specified URL. HTML
|
||||
* encoding is resolved following the attempts in the sequence: 1. reading
|
||||
* Content-Type response header, 2. Analyzing META tags at the beginning of
|
||||
* the html, 3. Using platform's default charset.
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public TagNode clean(final URL url) throws IOException {
|
||||
return clean(url, null);
|
||||
}
|
||||
|
||||
public TagNode clean(final URL url, String charset) throws IOException {
|
||||
final URLConnection urlConnection = url.openConnection();
|
||||
if (charset == null) {
|
||||
charset = Utils.getCharsetFromContentTypeString(urlConnection.getHeaderField("Content-Type"));
|
||||
}
|
||||
if (charset == null) {
|
||||
charset = Utils.getCharsetFromContent(url);
|
||||
}
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
return clean(url.openStream(), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all unclosed tags if there are any.
|
||||
*/
|
||||
private void closeAll(final List<BaseToken> nodeList, final CleanTimeValues cleanTimeValues) {
|
||||
final TagPos firstTagPos = cleanTimeValues.openTags.findFirstTagPos();
|
||||
if (firstTagPos != null) {
|
||||
closeSnippet(nodeList, firstTagPos, null, cleanTimeValues);
|
||||
}
|
||||
}
|
||||
|
||||
private List closeSnippet(final List nodeList, final TagPos tagPos, final Object toNode,
|
||||
final CleanTimeValues cleanTimeValues) {
|
||||
final List closed = new ArrayList();
|
||||
final ListIterator it = nodeList.listIterator(tagPos.position);
|
||||
|
||||
TagNode tagNode = null;
|
||||
Object item = it.next();
|
||||
boolean isListEnd = false;
|
||||
|
||||
while ((toNode == null && !isListEnd) || (toNode != null && item != toNode)) {
|
||||
if (isStartToken(item)) {
|
||||
final TagNode startTagToken = (TagNode) item;
|
||||
closed.add(startTagToken);
|
||||
final List<BaseToken> itemsToMove = startTagToken.getItemsToMove();
|
||||
if (itemsToMove != null) {
|
||||
final OpenTags prevOpenTags = cleanTimeValues.openTags;
|
||||
cleanTimeValues.openTags = new OpenTags();
|
||||
makeTree(itemsToMove, itemsToMove.listIterator(0), cleanTimeValues);
|
||||
closeAll(itemsToMove, cleanTimeValues);
|
||||
startTagToken.setItemsToMove(null);
|
||||
cleanTimeValues.openTags = prevOpenTags;
|
||||
}
|
||||
|
||||
final TagNode newTagNode = createTagNode(startTagToken);
|
||||
final TagInfo tag = tagInfoProvider.getTagInfo(newTagNode.getName());
|
||||
addPossibleHeadCandidate(tag, newTagNode, cleanTimeValues);
|
||||
if (tagNode != null) {
|
||||
tagNode.addChildren(itemsToMove);
|
||||
tagNode.addChild(newTagNode);
|
||||
it.set(null);
|
||||
} else {
|
||||
if (itemsToMove != null) {
|
||||
itemsToMove.add(newTagNode);
|
||||
it.set(itemsToMove);
|
||||
} else {
|
||||
it.set(newTagNode);
|
||||
}
|
||||
}
|
||||
|
||||
cleanTimeValues.openTags.removeTag(newTagNode.getName());
|
||||
tagNode = newTagNode;
|
||||
} else {
|
||||
if (tagNode != null) {
|
||||
it.set(null);
|
||||
if (item != null) {
|
||||
tagNode.addChild(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (it.hasNext()) {
|
||||
item = it.next();
|
||||
} else {
|
||||
isListEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
return closed;
|
||||
}
|
||||
|
||||
private void createDocumentNodes(final List listNodes, final CleanTimeValues cleanTimeValues) {
|
||||
final Iterator it = listNodes.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Object child = it.next();
|
||||
|
||||
if (child == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean toAdd = true;
|
||||
|
||||
if (child instanceof TagNode) {
|
||||
final TagNode node = (TagNode) child;
|
||||
final TagInfo tag = tagInfoProvider.getTagInfo(node.getName());
|
||||
addPossibleHeadCandidate(tag, node, cleanTimeValues);
|
||||
} else {
|
||||
if (child instanceof ContentNode) {
|
||||
toAdd = !"".equals(child.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (toAdd) {
|
||||
cleanTimeValues.bodyNode.addChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
// move all viable head candidates to head section of the tree
|
||||
final Iterator headIterator = cleanTimeValues.headTags.iterator();
|
||||
while (headIterator.hasNext()) {
|
||||
final TagNode headCandidateNode = (TagNode) headIterator.next();
|
||||
|
||||
// check if this node is already inside a candidate for moving to
|
||||
// head
|
||||
TagNode parent = headCandidateNode.getParent();
|
||||
boolean toMove = true;
|
||||
while (parent != null) {
|
||||
if (cleanTimeValues.headTags.contains(parent)) {
|
||||
toMove = false;
|
||||
break;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
if (toMove) {
|
||||
headCandidateNode.removeFromTree();
|
||||
cleanTimeValues.headNode.addChild(headCandidateNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TagNode createTagNode(final String name, final CleanTimeValues cleanTimeValues) {
|
||||
final TagNode node = new TagNode(name);
|
||||
if (cleanTimeValues.pruneTagSet != null && name != null
|
||||
&& cleanTimeValues.pruneTagSet.contains(name.toLowerCase())) {
|
||||
cleanTimeValues.pruneNodeSet.add(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private TagNode createTagNode(final TagNode startTagToken) {
|
||||
startTagToken.setFormed();
|
||||
return startTagToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the specified node, returns it's content as string.
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
public String getInnerHtml(final TagNode node) {
|
||||
if (node != null) {
|
||||
try {
|
||||
final String content = new SimpleXmlSerializer(properties).getAsString(node);
|
||||
int index1 = content.indexOf("<" + node.getName());
|
||||
index1 = content.indexOf('>', index1 + 1);
|
||||
final int index2 = content.lastIndexOf('<');
|
||||
return index1 >= 0 && index1 <= index2 ? content.substring(index1 + 1, index2) : null;
|
||||
} catch (final IOException e) {
|
||||
throw new HtmlCleanerException(e);
|
||||
}
|
||||
} else {
|
||||
throw new HtmlCleanerException("Cannot return inner html of the null node!");
|
||||
}
|
||||
}
|
||||
|
||||
public CleanerProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ITagInfoProvider instance for this HtmlCleaner
|
||||
*/
|
||||
public ITagInfoProvider getTagInfoProvider() {
|
||||
return tagInfoProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Transormations defined for this instance of cleaner
|
||||
*/
|
||||
public CleanerTransformations getTransformations() {
|
||||
return transformations;
|
||||
}
|
||||
|
||||
private boolean isAllowedInLastOpenTag(final BaseToken token, final CleanTimeValues cleanTimeValues) {
|
||||
final TagPos last = cleanTimeValues.openTags.getLastTagPos();
|
||||
if (last != null) {
|
||||
if (last.info != null) {
|
||||
return last.info.allowsItem(token);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if open fatal tag is missing if there is a fatal tag for the
|
||||
* specified tag.
|
||||
*
|
||||
* @param tag
|
||||
*/
|
||||
private boolean isFatalTagSatisfied(final TagInfo tag, final CleanTimeValues cleanTimeValues) {
|
||||
if (tag != null) {
|
||||
final String fatalTagName = tag.getFatalTag();
|
||||
return fatalTagName == null ? true : cleanTimeValues.openTags.tagExists(fatalTagName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isStartToken(final Object o) {
|
||||
return (o instanceof TagNode) && !((TagNode) o).isFormed();
|
||||
}
|
||||
|
||||
private TagNode makeTagNodeCopy(final TagNode tagNode, final CleanTimeValues cleanTimeValues) {
|
||||
final TagNode copy = tagNode.makeCopy();
|
||||
if (cleanTimeValues.pruneTagSet != null && cleanTimeValues.pruneTagSet.contains(tagNode.getName())) {
|
||||
cleanTimeValues.pruneNodeSet.add(copy);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
public void makeTree(final List<BaseToken> nodeList, final ListIterator<BaseToken> nodeIterator,
|
||||
final CleanTimeValues cleanTimeValues) {
|
||||
// process while not reach the end of the list
|
||||
while (nodeIterator.hasNext()) {
|
||||
final BaseToken token = nodeIterator.next();
|
||||
|
||||
if (token instanceof EndTagToken) {
|
||||
final EndTagToken endTagToken = (EndTagToken) token;
|
||||
final String tagName = endTagToken.getName();
|
||||
final TagInfo tag = tagInfoProvider.getTagInfo(tagName);
|
||||
|
||||
if ((tag == null && properties.isOmitUnknownTags())
|
||||
|| (tag != null && tag.isDeprecated() && properties.isOmitDeprecatedTags())) {
|
||||
nodeIterator.set(null);
|
||||
} else if (tag != null && !tag.allowsBody()) {
|
||||
nodeIterator.set(null);
|
||||
} else {
|
||||
final TagPos matchingPosition = cleanTimeValues.openTags.findTag(tagName);
|
||||
|
||||
if (matchingPosition != null) {
|
||||
final List closed = closeSnippet(nodeList, matchingPosition, endTagToken, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
for (int i = closed.size() - 1; i >= 1; i--) {
|
||||
final TagNode closedTag = (TagNode) closed.get(i);
|
||||
if (tag != null && tag.isContinueAfter(closedTag.getName())) {
|
||||
nodeIterator.add(makeTagNodeCopy(closedTag, cleanTimeValues));
|
||||
nodeIterator.previous();
|
||||
}
|
||||
}
|
||||
} else if (!isAllowedInLastOpenTag(token, cleanTimeValues)) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
}
|
||||
}
|
||||
} else if (isStartToken(token)) {
|
||||
final TagNode startTagToken = (TagNode) token;
|
||||
final String tagName = startTagToken.getName();
|
||||
final TagInfo tag = tagInfoProvider.getTagInfo(tagName);
|
||||
|
||||
final TagPos lastTagPos = cleanTimeValues.openTags.isEmpty() ? null : cleanTimeValues.openTags
|
||||
.getLastTagPos();
|
||||
final TagInfo lastTagInfo = lastTagPos == null ? null : tagInfoProvider.getTagInfo(lastTagPos.name);
|
||||
|
||||
// add tag to set of all tags
|
||||
cleanTimeValues.allTags.add(tagName);
|
||||
|
||||
// HTML open tag
|
||||
if ("html".equals(tagName)) {
|
||||
addAttributesToTag(cleanTimeValues.htmlNode, startTagToken.getAttributes());
|
||||
nodeIterator.set(null);
|
||||
// BODY open tag
|
||||
} else if ("body".equals(tagName)) {
|
||||
cleanTimeValues.bodyOpened = true;
|
||||
addAttributesToTag(cleanTimeValues.bodyNode, startTagToken.getAttributes());
|
||||
nodeIterator.set(null);
|
||||
// HEAD open tag
|
||||
} else if ("head".equals(tagName)) {
|
||||
cleanTimeValues.headOpened = true;
|
||||
addAttributesToTag(cleanTimeValues.headNode, startTagToken.getAttributes());
|
||||
nodeIterator.set(null);
|
||||
// unknown HTML tag and unknown tags are not allowed
|
||||
} else if ((tag == null && properties.isOmitUnknownTags())
|
||||
|| (tag != null && tag.isDeprecated() && properties.isOmitDeprecatedTags())) {
|
||||
nodeIterator.set(null);
|
||||
// if current tag is unknown, unknown tags are allowed and
|
||||
// last open tag doesn't allow any other tags in its body
|
||||
} else if (tag == null && lastTagInfo != null && !lastTagInfo.allowsAnything()) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
} else if (tag != null && tag.hasPermittedTags()
|
||||
&& cleanTimeValues.openTags.someAlreadyOpen(tag.getPermittedTags())) {
|
||||
nodeIterator.set(null);
|
||||
// if tag that must be unique, ignore this occurence
|
||||
} else if (tag != null && tag.isUnique() && cleanTimeValues.openTags.tagEncountered(tagName)) {
|
||||
nodeIterator.set(null);
|
||||
// if there is no required outer tag without that this open
|
||||
// tag is ignored
|
||||
} else if (!isFatalTagSatisfied(tag, cleanTimeValues)) {
|
||||
nodeIterator.set(null);
|
||||
// if there is no required parent tag - it must be added
|
||||
// before this open tag
|
||||
} else if (mustAddRequiredParent(tag, cleanTimeValues)) {
|
||||
final String requiredParent = tag.getRequiredParent();
|
||||
final TagNode requiredParentStartToken = createTagNode(requiredParent, cleanTimeValues);
|
||||
nodeIterator.previous();
|
||||
nodeIterator.add(requiredParentStartToken);
|
||||
nodeIterator.previous();
|
||||
// if last open tag has lower presidence then this, it must
|
||||
// be closed
|
||||
} else if (tag != null && lastTagPos != null && tag.isMustCloseTag(lastTagInfo)) {
|
||||
final List closed = closeSnippet(nodeList, lastTagPos, startTagToken, cleanTimeValues);
|
||||
final int closedCount = closed.size();
|
||||
|
||||
// it is needed to copy some tags again in front of current,
|
||||
// if there are any
|
||||
if (tag.hasCopyTags() && closedCount > 0) {
|
||||
// first iterates over list from the back and collects
|
||||
// all start tokens
|
||||
// in sequence that must be copied
|
||||
final ListIterator closedIt = closed.listIterator(closedCount);
|
||||
final List toBeCopied = new ArrayList();
|
||||
while (closedIt.hasPrevious()) {
|
||||
final TagNode currStartToken = (TagNode) closedIt.previous();
|
||||
if (tag.isCopy(currStartToken.getName())) {
|
||||
toBeCopied.add(0, currStartToken);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toBeCopied.size() > 0) {
|
||||
final Iterator copyIt = toBeCopied.iterator();
|
||||
while (copyIt.hasNext()) {
|
||||
final TagNode currStartToken = (TagNode) copyIt.next();
|
||||
nodeIterator.add(makeTagNodeCopy(currStartToken, cleanTimeValues));
|
||||
}
|
||||
|
||||
// back to the previous place, before adding new
|
||||
// start tokens
|
||||
for (int i = 0; i < toBeCopied.size(); i++) {
|
||||
nodeIterator.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeIterator.previous();
|
||||
// if this open tag is not allowed inside last open tag,
|
||||
// then it must be moved to the place where it can be
|
||||
} else if (!isAllowedInLastOpenTag(token, cleanTimeValues)) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
// if it is known HTML tag but doesn't allow body, it is
|
||||
// immediately closed
|
||||
} else if (tag != null && !tag.allowsBody()) {
|
||||
final TagNode newTagNode = createTagNode(startTagToken);
|
||||
addPossibleHeadCandidate(tag, newTagNode, cleanTimeValues);
|
||||
nodeIterator.set(newTagNode);
|
||||
// default case - just remember this open tag and go further
|
||||
} else {
|
||||
cleanTimeValues.openTags.addTag(tagName, nodeIterator.previousIndex());
|
||||
}
|
||||
} else {
|
||||
if (!isAllowedInLastOpenTag(token, cleanTimeValues)) {
|
||||
saveToLastOpenTag(nodeList, token, cleanTimeValues);
|
||||
nodeIterator.set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if specified tag requires parent tag, but that parent tag is
|
||||
* missing in the appropriate context.
|
||||
*
|
||||
* @param tag
|
||||
*/
|
||||
private boolean mustAddRequiredParent(final TagInfo tag, final CleanTimeValues cleanTimeValues) {
|
||||
if (tag != null) {
|
||||
final String requiredParent = tag.getRequiredParent();
|
||||
if (requiredParent != null) {
|
||||
final String fatalTag = tag.getFatalTag();
|
||||
int fatalTagPositon = -1;
|
||||
if (fatalTag != null) {
|
||||
final TagPos tagPos = cleanTimeValues.openTags.findTag(fatalTag);
|
||||
if (tagPos != null) {
|
||||
fatalTagPositon = tagPos.position;
|
||||
}
|
||||
}
|
||||
|
||||
// iterates through the list of open tags from the end and check
|
||||
// if there is some higher
|
||||
final ListIterator<TagPos> it = cleanTimeValues.openTags.list
|
||||
.listIterator(cleanTimeValues.openTags.list.size());
|
||||
while (it.hasPrevious()) {
|
||||
final TagPos currTagPos = it.previous();
|
||||
if (tag.isHigher(currTagPos.name)) {
|
||||
return currTagPos.position <= fatalTagPositon;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveToLastOpenTag(final List nodeList, final BaseToken tokenToAdd,
|
||||
final CleanTimeValues cleanTimeValues) {
|
||||
final TagPos last = cleanTimeValues.openTags.getLastTagPos();
|
||||
if (last != null && last.info != null && last.info.isIgnorePermitted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TagPos rubbishPos = cleanTimeValues.openTags.findTagToPlaceRubbish();
|
||||
if (rubbishPos != null) {
|
||||
final TagNode startTagToken = (TagNode) nodeList.get(rubbishPos.position);
|
||||
startTagToken.addItemForMoving(tokenToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the specified tag node, defines it's html content. This causes
|
||||
* cleaner to reclean given html portion and insert it inside the node
|
||||
* instead of previous content.
|
||||
*
|
||||
* @param node
|
||||
* @param content
|
||||
*/
|
||||
public void setInnerHtml(final TagNode node, final String content) {
|
||||
if (node != null) {
|
||||
final String nodeName = node.getName();
|
||||
final StringBuilder html = new StringBuilder();
|
||||
html.append("<" + nodeName + " marker=''>");
|
||||
html.append(content);
|
||||
html.append("</" + nodeName + ">");
|
||||
TagNode parent = node.getParent();
|
||||
while (parent != null) {
|
||||
final String parentName = parent.getName();
|
||||
html.insert(0, "<" + parentName + ">");
|
||||
html.append("</" + parentName + ">");
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
final TagNode rootNode = clean(html.toString());
|
||||
final TagNode cleanedNode = rootNode.findElementHavingAttribute("marker", true);
|
||||
if (cleanedNode != null) {
|
||||
node.setChildren(cleanedNode.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPruneTags(final String pruneTags, final CleanTimeValues cleanTimeValues) {
|
||||
cleanTimeValues.pruneTagSet.clear();
|
||||
cleanTimeValues.pruneNodeSet.clear();
|
||||
if (pruneTags != null) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(pruneTags, ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
cleanTimeValues.pruneTagSet.add(tokenizer.nextToken().trim().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tranformations for this cleaner instance.
|
||||
*
|
||||
* @param transformations
|
||||
*/
|
||||
public void setTransformations(final CleanerTransformations transformations) {
|
||||
this.transformations = transformations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* General HtmlCleaner runtime exception.
|
||||
* </p>
|
||||
*/
|
||||
public class HtmlCleanerException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public HtmlCleanerException() {
|
||||
this("HtmlCleaner expression occureed!");
|
||||
}
|
||||
|
||||
public HtmlCleanerException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HtmlCleanerException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public HtmlCleanerException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* Marker interface denoting nodes of the document tree
|
||||
*/
|
||||
public interface HtmlNode {
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract HTML serializer - contains common logic for descendants.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class HtmlSerializer extends Serializer {
|
||||
protected HtmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected boolean isMinimizedTagSyntax(final TagNode tagNode) {
|
||||
final TagInfo tagInfo = props.getTagInfoProvider().getTagInfo(tagNode.getName());
|
||||
return tagInfo != null && !tagNode.hasChildren() && tagInfo.isEmptyTag();
|
||||
}
|
||||
|
||||
protected boolean dontEscape(TagNode tagNode) {
|
||||
return isScriptOrStyle(tagNode);
|
||||
}
|
||||
|
||||
protected String escapeText(String s) {
|
||||
boolean recognizeUnicodeChars = props.isRecognizeUnicodeChars();
|
||||
boolean translateSpecialEntities = props.isTranslateSpecialEntities();
|
||||
|
||||
if (s != null) {
|
||||
int len = s.length();
|
||||
StringBuilder result = new StringBuilder(len);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
if (ch == '&') {
|
||||
if (i < len - 2 && s.charAt(i + 1) == '#') {
|
||||
boolean isHex = Character.toLowerCase(s.charAt(i + 2)) == 'x';
|
||||
int charIndex = i + (isHex ? 3 : 2);
|
||||
int radix = isHex ? 16 : 10;
|
||||
String unicode = "";
|
||||
while (charIndex < len) {
|
||||
char currCh = s.charAt(charIndex);
|
||||
if (currCh == ';') {
|
||||
break;
|
||||
} else if (Utils.isValidInt(unicode + currCh, radix)) {
|
||||
unicode += currCh;
|
||||
charIndex++;
|
||||
} else {
|
||||
charIndex--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.isValidInt(unicode, radix)) {
|
||||
char unicodeChar = (char) Integer.parseInt(unicode, radix);
|
||||
if (!Utils.isValidXmlChar(unicodeChar)) {
|
||||
i = charIndex;
|
||||
} else if (!Utils.isReservedXmlChar(unicodeChar)) {
|
||||
result.append(recognizeUnicodeChars ? String.valueOf(unicodeChar) : "&#" + unicode
|
||||
+ ";");
|
||||
i = charIndex;
|
||||
} else {
|
||||
i = charIndex;
|
||||
result.append("&#" + unicode + ";");
|
||||
}
|
||||
} else {
|
||||
result.append(props.isTransResCharsToNCR() ? "&#" + (int) '&' + ";" : "&");
|
||||
}
|
||||
} else {
|
||||
// get minimal following sequence required to recognize
|
||||
// some special entitiy
|
||||
String seq = s.substring(i, i + Math.min(SpecialEntity.getMaxEntityLength() + 2, len - i));
|
||||
int semiIndex = seq.indexOf(';');
|
||||
if (semiIndex > 0) {
|
||||
String entityKey = seq.substring(1, semiIndex);
|
||||
SpecialEntity entity = SpecialEntity.getEntity(entityKey);
|
||||
if (entity != null) {
|
||||
if (translateSpecialEntities) {
|
||||
result.append(props.isTransSpecialEntitiesToNCR() ? entity.getDecimalNCR() : entity
|
||||
.getCharacter());
|
||||
} else {
|
||||
result.append(entity.getEscapedValue());
|
||||
}
|
||||
|
||||
i += entityKey.length() + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String sub = s.substring(i);
|
||||
boolean isReservedSeq = false;
|
||||
for (Map.Entry<Character, String> entry : Utils.RESERVED_XML_CHARS.entrySet()) {
|
||||
seq = entry.getValue();
|
||||
if (sub.startsWith(seq)) {
|
||||
result.append(props.isTransResCharsToNCR() ? "&#" + (int) entry.getKey() + ";" : seq);
|
||||
i += seq.length() - 1;
|
||||
isReservedSeq = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isReservedSeq) {
|
||||
result.append(props.isTransResCharsToNCR() ? "&#" + (int) '&' + ";" : "&");
|
||||
}
|
||||
}
|
||||
} else if (Utils.isReservedXmlChar(ch)) {
|
||||
result.append(props.isTransResCharsToNCR() ? "&#" + (int) ch + ";" : ch);
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void serializeOpenTag(TagNode tagNode, Writer writer, boolean newLine) throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean nsAware = props.isNamespacesAware();
|
||||
|
||||
if (!nsAware && Utils.getXmlNSPrefix(tagName) != null) {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
|
||||
writer.write("<" + tagName);
|
||||
for (Map.Entry<String, String> entry : tagNode.getAttributes().entrySet()) {
|
||||
String attName = entry.getKey();
|
||||
if (!nsAware && Utils.getXmlNSPrefix(attName) != null) {
|
||||
attName = Utils.getXmlName(attName);
|
||||
}
|
||||
writer.write(" " + attName + "=\"" + escapeText(entry.getValue()) + "\"");
|
||||
}
|
||||
|
||||
if (nsAware) {
|
||||
Map<String, String> nsDeclarations = tagNode.getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (Map.Entry<String, String> entry : nsDeclarations.entrySet()) {
|
||||
String prefix = entry.getKey();
|
||||
String att = "xmlns";
|
||||
if (prefix.length() > 0) {
|
||||
att += ":" + prefix;
|
||||
}
|
||||
writer.write(" " + att + "=\"" + escapeText(entry.getValue()) + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMinimizedTagSyntax(tagNode)) {
|
||||
writer.write(" />");
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
} else {
|
||||
writer.write(">");
|
||||
}
|
||||
}
|
||||
|
||||
protected void serializeEndTag(TagNode tagNode, Writer writer, boolean newLine) throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.getXmlNSPrefix(tagName) != null && !props.isNamespacesAware()) {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
|
||||
writer.write("</" + tagName + ">");
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Main HTML tokenizer.
|
||||
* <p>
|
||||
* It's task is to parse HTML and produce list of valid tokens: open tag tokens,
|
||||
* end tag tokens, contents (text) and comments. As soon as new item is added to
|
||||
* token list, cleaner is invoked to clean current list at the end.
|
||||
* </p>
|
||||
*/
|
||||
abstract public class HtmlTokenizer {
|
||||
private final static int WORKING_BUFFER_SIZE = 1024;
|
||||
|
||||
private BufferedReader reader;
|
||||
private char[] working = new char[WORKING_BUFFER_SIZE];
|
||||
private transient int pos = 0;
|
||||
private transient int len = -1;
|
||||
private transient char saved[] = new char[512];
|
||||
private transient int savedLen = 0;
|
||||
private transient DoctypeToken docType = null;
|
||||
private transient TagToken currentTagToken = null;
|
||||
private transient List<BaseToken> tokenList = new ArrayList<BaseToken>();
|
||||
private boolean asExpected = true;
|
||||
private boolean isScriptContext = false;
|
||||
private CleanerProperties props;
|
||||
private boolean isOmitUnknownTags;
|
||||
private boolean isTreatUnknownTagsAsContent;
|
||||
private boolean isOmitDeprecatedTags;
|
||||
private boolean isTreatDeprecatedTagsAsContent;
|
||||
private boolean isNamespacesAware;
|
||||
private boolean isOmitComments;
|
||||
private boolean isAllowMultiWordAttributes;
|
||||
private boolean isAllowHtmlInsideAttributes;
|
||||
private CleanerTransformations transformations;
|
||||
private ITagInfoProvider tagInfoProvider;
|
||||
private StringBuilder commonStr = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Constructor - cretes instance of the parser with specified content.
|
||||
*
|
||||
* @param reader
|
||||
* @param props
|
||||
* @param transformations
|
||||
* @param tagInfoProvider
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public HtmlTokenizer(final Reader reader, final CleanerProperties props,
|
||||
final CleanerTransformations transformations, final ITagInfoProvider tagInfoProvider) throws IOException {
|
||||
this.reader = new BufferedReader(reader);
|
||||
this.props = props;
|
||||
this.isOmitUnknownTags = props.isOmitUnknownTags();
|
||||
this.isTreatUnknownTagsAsContent = props.isTreatUnknownTagsAsContent();
|
||||
this.isOmitDeprecatedTags = props.isOmitDeprecatedTags();
|
||||
this.isTreatDeprecatedTagsAsContent = props.isTreatDeprecatedTagsAsContent();
|
||||
this.isNamespacesAware = props.isNamespacesAware();
|
||||
this.isOmitComments = props.isOmitComments();
|
||||
this.isAllowMultiWordAttributes = props.isAllowMultiWordAttributes();
|
||||
this.isAllowHtmlInsideAttributes = props.isAllowHtmlInsideAttributes();
|
||||
this.transformations = transformations;
|
||||
this.tagInfoProvider = tagInfoProvider;
|
||||
}
|
||||
|
||||
private boolean addSavedAsContent() {
|
||||
if (savedLen > 0) {
|
||||
addToken(new ContentNode(saved, savedLen));
|
||||
savedLen = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addToken(final BaseToken token) {
|
||||
tokenList.add(token);
|
||||
makeTree(tokenList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single tag attribute - it is expected to be in one of the forms:
|
||||
* name=value name="value" name='value' name
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private String attributeValue() throws IOException {
|
||||
skipWhitespaces();
|
||||
|
||||
if (isCharSimple('<') || isCharSimple('>') || startsWithSimple("/>")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
boolean isQuoteMode = false;
|
||||
boolean isAposMode = false;
|
||||
|
||||
commonStr.delete(0, commonStr.length());
|
||||
if (isCharSimple('\'')) {
|
||||
isAposMode = true;
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
} else if (isCharSimple('\"')) {
|
||||
isQuoteMode = true;
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
}
|
||||
|
||||
while (!isAllRead()
|
||||
&& (((isAposMode && !isCharEquals('\'') || isQuoteMode && !isCharEquals('\"'))
|
||||
&& (isAllowHtmlInsideAttributes || !isCharEquals('>') && !isCharEquals('<')) && (isAllowMultiWordAttributes || !isWhitespaceSafe())) || (!isAposMode
|
||||
&& !isQuoteMode && !isWhitespaceSafe() && !isCharEquals('>') && !isCharEquals('<')))) {
|
||||
if (isValidXmlCharSafe()) {
|
||||
commonStr.append(working[pos]);
|
||||
saveCurrentSafe();
|
||||
}
|
||||
go();
|
||||
}
|
||||
|
||||
if (isCharSimple('\'') && isAposMode) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
} else if (isCharSimple('\"') && isQuoteMode) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
}
|
||||
|
||||
return commonStr.toString();
|
||||
}
|
||||
|
||||
private void comment() throws IOException {
|
||||
go(4);
|
||||
while (!isAllRead() && !startsWithSimple("-->")) {
|
||||
if (isValidXmlCharSafe()) {
|
||||
saveCurrentSafe();
|
||||
}
|
||||
go();
|
||||
}
|
||||
|
||||
if (startsWithSimple("-->")) {
|
||||
go(3);
|
||||
}
|
||||
|
||||
if (savedLen > 0) {
|
||||
if (!isOmitComments) {
|
||||
final String hyphenRepl = props.getHyphenReplacementInComment();
|
||||
String comment = new String(saved, 0, savedLen).replaceAll("--", hyphenRepl + hyphenRepl);
|
||||
|
||||
if (comment.length() > 0 && comment.charAt(0) == '-') {
|
||||
comment = hyphenRepl + comment.substring(1);
|
||||
}
|
||||
final int len = comment.length();
|
||||
if (len > 0 && comment.charAt(len - 1) == '-') {
|
||||
comment = comment.substring(0, len - 1) + hyphenRepl;
|
||||
}
|
||||
|
||||
addToken(new CommentNode(comment));
|
||||
}
|
||||
savedLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean content() throws IOException {
|
||||
while (!isAllRead()) {
|
||||
if (isValidXmlCharSafe()) {
|
||||
saveCurrentSafe();
|
||||
}
|
||||
go();
|
||||
|
||||
if (isCharSimple('<')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return addSavedAsContent();
|
||||
}
|
||||
|
||||
public abstract TagNode createTagNode(String name);
|
||||
|
||||
private void doctype() throws IOException {
|
||||
go(9);
|
||||
|
||||
skipWhitespaces();
|
||||
final String part1 = identifier();
|
||||
skipWhitespaces();
|
||||
final String part2 = identifier();
|
||||
skipWhitespaces();
|
||||
final String part3 = attributeValue();
|
||||
skipWhitespaces();
|
||||
final String part4 = attributeValue();
|
||||
|
||||
ignoreUntil('<');
|
||||
|
||||
docType = new DoctypeToken(part1, part2, part3, part4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current character to be read, but first it must be checked if it
|
||||
* exists. This method is made for performance reasons to be used
|
||||
* instead of isChar(...).
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private char getCurrentChar() {
|
||||
return working[pos];
|
||||
}
|
||||
|
||||
public DoctypeToken getDocType() {
|
||||
return docType;
|
||||
}
|
||||
|
||||
public List<BaseToken> getTokenList() {
|
||||
return this.tokenList;
|
||||
}
|
||||
|
||||
private void go() throws IOException {
|
||||
pos++;
|
||||
readIfNeeded(0);
|
||||
}
|
||||
|
||||
private void go(final int step) throws IOException {
|
||||
pos += step;
|
||||
readIfNeeded(step - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an identifier from the current position.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private String identifier() throws IOException {
|
||||
asExpected = true;
|
||||
|
||||
if (!isIdentifierStartChar()) {
|
||||
asExpected = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
commonStr.delete(0, commonStr.length());
|
||||
|
||||
while (!isAllRead() && isIdentifierChar()) {
|
||||
saveCurrentSafe();
|
||||
commonStr.append(working[pos]);
|
||||
go();
|
||||
}
|
||||
|
||||
// strip invalid characters from the end
|
||||
while (commonStr.length() > 0 && Utils.isIdentifierHelperChar(commonStr.charAt(commonStr.length() - 1))) {
|
||||
commonStr.deleteCharAt(commonStr.length() - 1);
|
||||
}
|
||||
|
||||
if (commonStr.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String id = commonStr.toString();
|
||||
|
||||
final int columnIndex = id.indexOf(':');
|
||||
if (columnIndex >= 0) {
|
||||
final String prefix = id.substring(0, columnIndex);
|
||||
String suffix = id.substring(columnIndex + 1);
|
||||
final int nextColumnIndex = suffix.indexOf(':');
|
||||
if (nextColumnIndex >= 0) {
|
||||
suffix = suffix.substring(0, nextColumnIndex);
|
||||
}
|
||||
id = isNamespacesAware ? (prefix + ":" + suffix) : suffix;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private void ignoreUntil(final char ch) throws IOException {
|
||||
while (!isAllRead()) {
|
||||
go();
|
||||
if (isChar(ch)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if end of the content is reached.
|
||||
*/
|
||||
private boolean isAllRead() {
|
||||
return len >= 0 && pos >= len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position is equal to specified
|
||||
* char.
|
||||
*
|
||||
* @param ch
|
||||
* @return true is equal, false otherwise.
|
||||
*/
|
||||
private boolean isChar(final char ch) {
|
||||
return isChar(pos, ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at specified position is equal to specified char.
|
||||
*
|
||||
* @param position
|
||||
* @param ch
|
||||
* @return true is equals, false otherwise.
|
||||
*/
|
||||
private boolean isChar(final int position, final char ch) {
|
||||
if (len >= 0 && position >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Character.toLowerCase(ch) == Character.toLowerCase(working[position]);
|
||||
}
|
||||
|
||||
private boolean isCharEquals(final char ch) {
|
||||
return working[pos] == ch;
|
||||
}
|
||||
|
||||
private boolean isCharSimple(final char ch) {
|
||||
return (len < 0 || pos < len) && (ch == working[pos]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position can be identifier part.
|
||||
*
|
||||
* @return true is may be identifier part, false otherwise.
|
||||
*/
|
||||
private boolean isIdentifierChar() {
|
||||
if (len >= 0 && pos >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final char ch = working[pos];
|
||||
return Character.isUnicodeIdentifierStart(ch) || Character.isDigit(ch) || Utils.isIdentifierHelperChar(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position can be identifier start.
|
||||
*
|
||||
* @return true is may be identifier start, false otherwise.
|
||||
*/
|
||||
private boolean isIdentifierStartChar() {
|
||||
return isIdentifierStartChar(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at specified position can be identifier start.
|
||||
*
|
||||
* @param position
|
||||
* @return true is may be identifier start, false otherwise.
|
||||
*/
|
||||
private boolean isIdentifierStartChar(final int position) {
|
||||
if (len >= 0 && position >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final char ch = working[position];
|
||||
return Character.isUnicodeIdentifierStart(ch) || ch == '_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified tag name is one of the reserved tags: HTML, HEAD or
|
||||
* BODY
|
||||
*
|
||||
* @param tagName
|
||||
* @return
|
||||
*/
|
||||
private boolean isReservedTag(String tagName) {
|
||||
tagName = tagName.toLowerCase();
|
||||
return "html".equals(tagName) || "head".equals(tagName) || "body".equals(tagName);
|
||||
}
|
||||
|
||||
private boolean isValidXmlChar() {
|
||||
return isAllRead() || Utils.isValidXmlChar(working[pos]);
|
||||
}
|
||||
|
||||
private boolean isValidXmlCharSafe() {
|
||||
return Utils.isValidXmlChar(working[pos]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at current runtime position is whitespace.
|
||||
*
|
||||
* @return true is whitespace, false otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private boolean isWhitespace() {
|
||||
return isWhitespace(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if character at specified position is whitespace.
|
||||
*
|
||||
* @param position
|
||||
* @return true is whitespace, false otherwise.
|
||||
*/
|
||||
private boolean isWhitespace(final int position) {
|
||||
if (len >= 0 && position >= len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Character.isWhitespace(working[position]);
|
||||
}
|
||||
|
||||
private boolean isWhitespaceSafe() {
|
||||
return Character.isWhitespace(working[pos]);
|
||||
}
|
||||
|
||||
public abstract void makeTree(List<BaseToken> tokenList);
|
||||
|
||||
private void readIfNeeded(final int neededChars) throws IOException {
|
||||
if (len == -1 && pos + neededChars >= WORKING_BUFFER_SIZE) {
|
||||
final int numToCopy = WORKING_BUFFER_SIZE - pos;
|
||||
System.arraycopy(working, pos, working, 0, numToCopy);
|
||||
pos = 0;
|
||||
|
||||
int expected = WORKING_BUFFER_SIZE - numToCopy;
|
||||
int size = 0;
|
||||
int charsRead;
|
||||
int offset = numToCopy;
|
||||
do {
|
||||
charsRead = reader.read(working, offset, expected);
|
||||
if (charsRead >= 0) {
|
||||
size += charsRead;
|
||||
offset += charsRead;
|
||||
expected -= charsRead;
|
||||
}
|
||||
} while (charsRead >= 0 && expected > 0);
|
||||
|
||||
if (expected > 0) {
|
||||
len = size + numToCopy;
|
||||
}
|
||||
|
||||
// convert invalid XML characters to spaces
|
||||
for (int i = 0; i < (len >= 0 ? len : WORKING_BUFFER_SIZE); i++) {
|
||||
final int ch = working[i];
|
||||
if (ch >= 1 && ch <= 32 && ch != 10 && ch != 13) {
|
||||
working[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves specified character to the temporary buffer.
|
||||
*
|
||||
* @param ch
|
||||
*/
|
||||
private void save(final char ch) {
|
||||
if (savedLen >= saved.length) {
|
||||
final char newSaved[] = new char[saved.length + 512];
|
||||
System.arraycopy(saved, 0, newSaved, 0, saved.length);
|
||||
saved = newSaved;
|
||||
}
|
||||
saved[savedLen++] = ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves character at current runtime position to the temporary buffer.
|
||||
*/
|
||||
private void saveCurrent() {
|
||||
if (!isAllRead()) {
|
||||
save(working[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves specified number of characters at current runtime position to the
|
||||
* temporary buffer.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void saveCurrent(int size) throws IOException {
|
||||
readIfNeeded(size);
|
||||
int pos = this.pos;
|
||||
while (!isAllRead() && (size > 0)) {
|
||||
save(working[pos]);
|
||||
pos++;
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveCurrentSafe() {
|
||||
save(working[pos]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips whitespaces at current position and moves foreward until
|
||||
* non-whitespace character is found or the end of content is reached.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void skipWhitespaces() throws IOException {
|
||||
while (!isAllRead() && isWhitespaceSafe()) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts parsing HTML.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void start() throws IOException {
|
||||
// initialize runtime values
|
||||
currentTagToken = null;
|
||||
tokenList.clear();
|
||||
asExpected = true;
|
||||
isScriptContext = false;
|
||||
|
||||
boolean isLateForDoctype = false;
|
||||
|
||||
this.pos = WORKING_BUFFER_SIZE;
|
||||
readIfNeeded(0);
|
||||
|
||||
boolean isScriptEmpty = true;
|
||||
|
||||
while (!isAllRead()) {
|
||||
// resets all the runtime values
|
||||
savedLen = 0;
|
||||
currentTagToken = null;
|
||||
asExpected = true;
|
||||
|
||||
// this is enough for making decision
|
||||
readIfNeeded(10);
|
||||
|
||||
if (isScriptContext) {
|
||||
if (startsWith("</script") && (isWhitespace(pos + 8) || isChar(pos + 8, '>'))) {
|
||||
tagEnd();
|
||||
} else if (isScriptEmpty && startsWithSimple("<!--")) {
|
||||
comment();
|
||||
} else {
|
||||
final boolean isTokenAdded = content();
|
||||
if (isScriptEmpty && isTokenAdded) {
|
||||
final BaseToken lastToken = tokenList.get(tokenList.size() - 1);
|
||||
if (lastToken != null) {
|
||||
final String lastTokenAsString = lastToken.toString();
|
||||
if (lastTokenAsString != null && lastTokenAsString.trim().length() > 0) {
|
||||
isScriptEmpty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isScriptContext) {
|
||||
isScriptEmpty = true;
|
||||
}
|
||||
} else {
|
||||
if (startsWith("<!doctype")) {
|
||||
if (!isLateForDoctype) {
|
||||
doctype();
|
||||
isLateForDoctype = true;
|
||||
} else {
|
||||
ignoreUntil('<');
|
||||
}
|
||||
} else if (startsWithSimple("</") && isIdentifierStartChar(pos + 2)) {
|
||||
isLateForDoctype = true;
|
||||
tagEnd();
|
||||
} else if (startsWithSimple("<!--")) {
|
||||
comment();
|
||||
} else if (startsWithSimple("<") && isIdentifierStartChar(pos + 1)) {
|
||||
isLateForDoctype = true;
|
||||
tagStart();
|
||||
} else if (props.isIgnoreQuestAndExclam() && (startsWithSimple("<!") || startsWithSimple("<?"))) {
|
||||
ignoreUntil('>');
|
||||
if (isCharSimple('>')) {
|
||||
go();
|
||||
}
|
||||
} else {
|
||||
content();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if content starts with specified value at the current position.
|
||||
*
|
||||
* @param value
|
||||
* @return true if starts with specified value, false otherwise.
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean startsWith(final String value) throws IOException {
|
||||
final int valueLen = value.length();
|
||||
readIfNeeded(valueLen);
|
||||
if (len >= 0 && pos + valueLen > len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueLen; i++) {
|
||||
final char ch1 = Character.toLowerCase(value.charAt(i));
|
||||
final char ch2 = Character.toLowerCase(working[pos + i]);
|
||||
if (ch1 != ch2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean startsWithSimple(final String value) throws IOException {
|
||||
final int valueLen = value.length();
|
||||
readIfNeeded(valueLen);
|
||||
if (len >= 0 && pos + valueLen > len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueLen; i++) {
|
||||
if (value.charAt(i) != working[pos + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses list tag attributes from the current position.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void tagAttributes() throws IOException {
|
||||
while (!isAllRead() && asExpected && !isCharSimple('>') && !startsWithSimple("/>")) {
|
||||
skipWhitespaces();
|
||||
final String attName = identifier();
|
||||
|
||||
if (!asExpected) {
|
||||
if (!isCharSimple('<') && !isCharSimple('>') && !startsWithSimple("/>")) {
|
||||
if (isValidXmlChar()) {
|
||||
saveCurrent();
|
||||
}
|
||||
go();
|
||||
}
|
||||
|
||||
if (!isCharSimple('<')) {
|
||||
asExpected = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
String attValue;
|
||||
|
||||
skipWhitespaces();
|
||||
if (isCharSimple('=')) {
|
||||
saveCurrentSafe();
|
||||
go();
|
||||
attValue = attributeValue();
|
||||
} else if (CleanerProperties.BOOL_ATT_EMPTY.equals(props.getBooleanAttributeValues())) {
|
||||
attValue = "";
|
||||
} else if (CleanerProperties.BOOL_ATT_TRUE.equals(props.getBooleanAttributeValues())) {
|
||||
attValue = "true";
|
||||
} else {
|
||||
attValue = attName;
|
||||
}
|
||||
|
||||
if (asExpected) {
|
||||
currentTagToken.setAttribute(attName, attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses end of the tag.
|
||||
* It expects that current position is at the "<" after which
|
||||
* "/" and the tag's name follows.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void tagEnd() throws IOException {
|
||||
saveCurrent(2);
|
||||
go(2);
|
||||
|
||||
if (isAllRead()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String tagName = identifier();
|
||||
if (transformations != null && transformations.hasTransformationForTag(tagName)) {
|
||||
final TagTransformation tagTransformation = transformations.getTransformation(tagName);
|
||||
if (tagTransformation != null) {
|
||||
tagName = tagTransformation.getDestTag();
|
||||
}
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
final TagInfo tagInfo = tagInfoProvider.getTagInfo(tagName);
|
||||
if ((tagInfo == null && !isOmitUnknownTags && isTreatUnknownTagsAsContent && !isReservedTag(tagName))
|
||||
|| (tagInfo != null && tagInfo.isDeprecated() && !isOmitDeprecatedTags && isTreatDeprecatedTagsAsContent)) {
|
||||
content();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentTagToken = new EndTagToken(tagName);
|
||||
|
||||
if (asExpected) {
|
||||
skipWhitespaces();
|
||||
tagAttributes();
|
||||
|
||||
if (tagName != null) {
|
||||
addToken(currentTagToken);
|
||||
}
|
||||
|
||||
if (isCharSimple('>')) {
|
||||
go();
|
||||
}
|
||||
|
||||
if ("script".equalsIgnoreCase(tagName)) {
|
||||
isScriptContext = false;
|
||||
}
|
||||
|
||||
currentTagToken = null;
|
||||
} else {
|
||||
addSavedAsContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses start of the tag.
|
||||
* It expects that current position is at the "<" after which
|
||||
* the tag's name follows.
|
||||
* @throws IOException
|
||||
*/
|
||||
private void tagStart() throws IOException {
|
||||
saveCurrent();
|
||||
go();
|
||||
|
||||
if (isAllRead()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String tagName = identifier();
|
||||
|
||||
TagTransformation tagTransformation = null;
|
||||
if (transformations != null && transformations.hasTransformationForTag(tagName)) {
|
||||
tagTransformation = transformations.getTransformation(tagName);
|
||||
if (tagTransformation != null) {
|
||||
tagName = tagTransformation.getDestTag();
|
||||
}
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
final TagInfo tagInfo = tagInfoProvider.getTagInfo(tagName);
|
||||
if ((tagInfo == null && !isOmitUnknownTags && isTreatUnknownTagsAsContent && !isReservedTag(tagName))
|
||||
|| (tagInfo != null && tagInfo.isDeprecated() && !isOmitDeprecatedTags && isTreatDeprecatedTagsAsContent)) {
|
||||
content();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final TagNode tagNode = createTagNode(tagName);
|
||||
currentTagToken = tagNode;
|
||||
|
||||
if (asExpected) {
|
||||
skipWhitespaces();
|
||||
tagAttributes();
|
||||
|
||||
if (tagName != null) {
|
||||
if (tagTransformation != null) {
|
||||
tagNode.transformAttributes(tagTransformation);
|
||||
}
|
||||
addToken(currentTagToken);
|
||||
}
|
||||
|
||||
if (isCharSimple('>')) {
|
||||
go();
|
||||
if ("script".equalsIgnoreCase(tagName)) {
|
||||
isScriptContext = true;
|
||||
}
|
||||
} else if (startsWithSimple("/>")) {
|
||||
go(2);
|
||||
if ("script".equalsIgnoreCase(tagName)) {
|
||||
addToken(new EndTagToken(tagName));
|
||||
}
|
||||
}
|
||||
|
||||
currentTagToken = null;
|
||||
} else {
|
||||
addSavedAsContent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides set of TagInfo instances. The instance of this interface is used as
|
||||
* a collection of tag definitions used in cleanup process. Implementing this
|
||||
* interface desired behaviour of cleaner can be achived.<br/>
|
||||
* In most cases implementation will be or contain a kind of Map.
|
||||
* </p>
|
||||
*/
|
||||
public interface ITagInfoProvider {
|
||||
TagInfo getTagInfo(String tagName);
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Pretty HTML serializer - creates resulting HTML with indenting lines.
|
||||
* </p>
|
||||
*/
|
||||
public class PrettyHtmlSerializer extends HtmlSerializer {
|
||||
private static final String DEFAULT_INDENTATION_STRING = "\t";
|
||||
|
||||
private String indentString = DEFAULT_INDENTATION_STRING;
|
||||
final private List<String> indents = new ArrayList<String>();
|
||||
|
||||
public PrettyHtmlSerializer(final CleanerProperties props) {
|
||||
this(props, DEFAULT_INDENTATION_STRING);
|
||||
}
|
||||
|
||||
public PrettyHtmlSerializer(final CleanerProperties props, final String indentString) {
|
||||
super(props);
|
||||
this.indentString = indentString;
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
serializePrettyHtml(tagNode, writer, 0, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param level
|
||||
* @return Appropriate indentation for the specified depth.
|
||||
*/
|
||||
private synchronized String getIndent(final int level) {
|
||||
final int size = indents.size();
|
||||
if (size <= level) {
|
||||
String prevIndent = size == 0 ? null : indents.get(size - 1);
|
||||
for (int i = size; i <= level; i++) {
|
||||
final String currIndent = prevIndent == null ? "" : prevIndent + indentString;
|
||||
indents.add(currIndent);
|
||||
prevIndent = currIndent;
|
||||
}
|
||||
}
|
||||
|
||||
return indents.get(level);
|
||||
}
|
||||
|
||||
private String getIndentedText(final String content, final int level) {
|
||||
final String indent = getIndent(level);
|
||||
final StringBuilder result = new StringBuilder(content.length());
|
||||
final StringTokenizer tokenizer = new StringTokenizer(content, "\n\r");
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String line = tokenizer.nextToken().trim();
|
||||
if (!"".equals(line)) {
|
||||
result.append(indent).append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String getSingleLineOfChildren(final List children) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
final Iterator childrenIt = children.iterator();
|
||||
boolean isFirst = true;
|
||||
|
||||
while (childrenIt.hasNext()) {
|
||||
final Object child = childrenIt.next();
|
||||
|
||||
if (!(child instanceof ContentNode)) {
|
||||
return null;
|
||||
} else {
|
||||
String content = child.toString();
|
||||
|
||||
// if first item trims it from left
|
||||
if (isFirst) {
|
||||
content = Utils.ltrim(content);
|
||||
}
|
||||
|
||||
// if last item trims it from right
|
||||
if (!childrenIt.hasNext()) {
|
||||
content = Utils.rtrim(content);
|
||||
}
|
||||
|
||||
if (content.indexOf('\n') >= 0 || content.indexOf('\r') >= 0) {
|
||||
return null;
|
||||
}
|
||||
result.append(content);
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected void serializePrettyHtml(final TagNode tagNode, final Writer writer, final int level,
|
||||
final boolean isPreserveWhitespaces, final boolean isLastNewLine) throws IOException {
|
||||
final List tagChildren = tagNode.getChildren();
|
||||
final String tagName = tagNode.getName();
|
||||
final boolean isHeadlessNode = Utils.isEmptyString(tagName);
|
||||
final String indent = isHeadlessNode ? "" : getIndent(level);
|
||||
|
||||
if (!isPreserveWhitespaces) {
|
||||
if (!isLastNewLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.write(indent);
|
||||
}
|
||||
serializeOpenTag(tagNode, writer, true);
|
||||
|
||||
final boolean preserveWhitespaces = isPreserveWhitespaces || "pre".equalsIgnoreCase(tagName);
|
||||
|
||||
boolean lastWasNewLine = false;
|
||||
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
final String singleLine = getSingleLineOfChildren(tagChildren);
|
||||
final boolean dontEscape = dontEscape(tagNode);
|
||||
if (!preserveWhitespaces && singleLine != null) {
|
||||
writer.write(!dontEscape(tagNode) ? escapeText(singleLine) : singleLine);
|
||||
} else {
|
||||
final Iterator childIterator = tagChildren.iterator();
|
||||
while (childIterator.hasNext()) {
|
||||
final Object child = childIterator.next();
|
||||
if (child instanceof TagNode) {
|
||||
serializePrettyHtml((TagNode) child, writer, isHeadlessNode ? level : level + 1,
|
||||
preserveWhitespaces, lastWasNewLine);
|
||||
lastWasNewLine = false;
|
||||
} else if (child instanceof ContentNode) {
|
||||
final String content = dontEscape ? child.toString() : escapeText(child.toString());
|
||||
if (content.length() > 0) {
|
||||
if (dontEscape || preserveWhitespaces) {
|
||||
writer.write(content);
|
||||
} else if (Character.isWhitespace(content.charAt(0))) {
|
||||
if (!lastWasNewLine) {
|
||||
writer.write("\n");
|
||||
lastWasNewLine = false;
|
||||
}
|
||||
if (content.trim().length() > 0) {
|
||||
writer.write(getIndentedText(Utils.rtrim(content), isHeadlessNode ? level
|
||||
: level + 1));
|
||||
} else {
|
||||
lastWasNewLine = true;
|
||||
}
|
||||
} else {
|
||||
if (content.trim().length() > 0) {
|
||||
writer.write(Utils.rtrim(content));
|
||||
}
|
||||
if (!childIterator.hasNext()) {
|
||||
writer.write("\n");
|
||||
lastWasNewLine = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (child instanceof CommentNode) {
|
||||
if (!lastWasNewLine && !preserveWhitespaces) {
|
||||
writer.write("\n");
|
||||
lastWasNewLine = false;
|
||||
}
|
||||
final CommentNode commentNode = (CommentNode) child;
|
||||
final String content = commentNode.getCommentedContent();
|
||||
writer.write(dontEscape ? content
|
||||
: getIndentedText(content, isHeadlessNode ? level : level + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (singleLine == null && !preserveWhitespaces) {
|
||||
if (!lastWasNewLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.write(indent);
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Basic abstract serializer - contains common logic for descendants (methods
|
||||
* <code>writeXXX()</code>.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class Serializer {
|
||||
|
||||
/**
|
||||
* Used to implement serialization with missing envelope - omiting open and
|
||||
* close tags, just serialize children.
|
||||
*/
|
||||
private class HeadlessTagNode extends TagNode {
|
||||
private HeadlessTagNode(final TagNode wrappedNode) {
|
||||
super("");
|
||||
getAttributes().putAll(wrappedNode.getAttributes());
|
||||
getChildren().addAll(wrappedNode.getChildren());
|
||||
setDocType(wrappedNode.getDocType());
|
||||
final Map<String, String> nsDecls = getNamespaceDeclarations();
|
||||
if (nsDecls != null) {
|
||||
final Map<String, String> wrappedNSDecls = wrappedNode.getNamespaceDeclarations();
|
||||
if (wrappedNSDecls != null) {
|
||||
nsDecls.putAll(wrappedNSDecls);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected CleanerProperties props;
|
||||
|
||||
protected Serializer(final CleanerProperties props) {
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode
|
||||
* Node to serialize to string
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(final TagNode tagNode) throws IOException {
|
||||
return getAsString(tagNode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode
|
||||
* Node to serialize to string
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(final TagNode tagNode, final boolean omitEnvelope) throws IOException {
|
||||
return getAsString(tagNode, HtmlCleaner.DEFAULT_CHARSET, omitEnvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode
|
||||
* Node to serialize to string
|
||||
* @param charset
|
||||
* Charset of the output - stands in xml declaration part
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(final TagNode tagNode, final String charset) throws IOException {
|
||||
return getAsString(tagNode, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagNode
|
||||
* Node to serialize to string
|
||||
* @param charset
|
||||
* Charset of the output - stands in xml declaration part
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @return Output as string
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getAsString(final TagNode tagNode, final String charset, final boolean omitEnvelope)
|
||||
throws IOException {
|
||||
final StringWriter writer = new StringWriter();
|
||||
write(tagNode, writer, charset, omitEnvelope);
|
||||
return writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
protected boolean isScriptOrStyle(final TagNode tagNode) {
|
||||
final String tagName = tagNode.getName();
|
||||
return "script".equalsIgnoreCase(tagName) || "style".equalsIgnoreCase(tagName);
|
||||
}
|
||||
|
||||
protected abstract void serialize(TagNode tagNode, Writer writer) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes specified node using specified writer.
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to serialize.
|
||||
* @param writer
|
||||
* Writer instance
|
||||
* @param charset
|
||||
* Charset of the output
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(final TagNode tagNode, final Writer writer, final String charset) throws IOException {
|
||||
write(tagNode, writer, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified node using specified writer.
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to serialize.
|
||||
* @param writer
|
||||
* Writer instance
|
||||
* @param charset
|
||||
* Charset of the output
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void write(TagNode tagNode, Writer writer, final String charset, final boolean omitEnvelope)
|
||||
throws IOException {
|
||||
if (omitEnvelope) {
|
||||
tagNode = new HeadlessTagNode(tagNode);
|
||||
}
|
||||
writer = new BufferedWriter(writer);
|
||||
if (!props.isOmitXmlDeclaration()) {
|
||||
String declaration = "<?xml version=\"1.0\"";
|
||||
if (charset != null) {
|
||||
declaration += " encoding=\"" + charset + "\"";
|
||||
}
|
||||
declaration += "?>";
|
||||
writer.write(declaration + "\n");
|
||||
}
|
||||
|
||||
if (!props.isOmitDoctypeDeclaration()) {
|
||||
final DoctypeToken doctypeToken = tagNode.getDocType();
|
||||
if (doctypeToken != null) {
|
||||
doctypeToken.serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serialize(tagNode, writer);
|
||||
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using system default charset.
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param fileName
|
||||
* Output file name
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(final TagNode tagNode, final String fileName) throws IOException {
|
||||
writeToFile(tagNode, fileName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using specified charset and
|
||||
* optionally omits node envelope (skips open and close tags of the node).
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param fileName
|
||||
* Output file name
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(final TagNode tagNode, final String fileName, final boolean omitEnvelope)
|
||||
throws IOException {
|
||||
writeToFile(tagNode, fileName, HtmlCleaner.DEFAULT_CHARSET, omitEnvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using specified charset.
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param fileName
|
||||
* Output file name
|
||||
* @param charset
|
||||
* Charset of the output
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(final TagNode tagNode, final String fileName, final String charset) throws IOException {
|
||||
writeToFile(tagNode, fileName, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the file, using specified charset and
|
||||
* optionally omits node envelope (skips open and close tags of the node).
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param fileName
|
||||
* Output file name
|
||||
* @param charset
|
||||
* Charset of the output
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToFile(final TagNode tagNode, final String fileName, final String charset,
|
||||
final boolean omitEnvelope) throws IOException {
|
||||
writeToStream(tagNode, new FileOutputStream(fileName), charset, omitEnvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using system default
|
||||
* charset.
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param out
|
||||
* Output stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(final TagNode tagNode, final OutputStream out) throws IOException {
|
||||
writeToStream(tagNode, out, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using system default
|
||||
* charset and optionally omits node envelope (skips open and close tags of
|
||||
* the node).
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param out
|
||||
* Output stream
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(final TagNode tagNode, final OutputStream out, final boolean omitEnvelope)
|
||||
throws IOException {
|
||||
writeToStream(tagNode, out, HtmlCleaner.DEFAULT_CHARSET, omitEnvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using specified charset.
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param out
|
||||
* Output stream
|
||||
* @param charset
|
||||
* Charset of the output
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(final TagNode tagNode, final OutputStream out, final String charset) throws IOException {
|
||||
writeToStream(tagNode, out, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes specified TagNode to the output stream, using specified charset
|
||||
* and optionally omits node envelope (skips open and close tags of the
|
||||
* node).
|
||||
*
|
||||
* @param tagNode
|
||||
* Node to be written
|
||||
* @param out
|
||||
* Output stream
|
||||
* @param charset
|
||||
* Charset of the output
|
||||
* @param omitEnvelope
|
||||
* Tells whether to skip open and close tag of the node.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeToStream(final TagNode tagNode, final OutputStream out, final String charset,
|
||||
final boolean omitEnvelope) throws IOException {
|
||||
write(tagNode, new OutputStreamWriter(out, charset), charset, omitEnvelope);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Simple HTML serializer - creates resulting HTML without indenting and/or
|
||||
* compacting.
|
||||
* </p>
|
||||
*/
|
||||
public class SimpleHtmlSerializer extends HtmlSerializer {
|
||||
public SimpleHtmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
for (final Object item : tagNode.getChildren()) {
|
||||
if (item instanceof ContentNode) {
|
||||
final String content = item.toString();
|
||||
writer.write(dontEscape(tagNode) ? content : escapeText(content));
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken) item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Simple XML serializer - creates resulting XML without indenting lines.
|
||||
* </p>
|
||||
*/
|
||||
public class SimpleXmlSerializer extends XmlSerializer {
|
||||
|
||||
public SimpleXmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
protected void serialize(final TagNode tagNode, final Writer writer) throws IOException {
|
||||
serializeOpenTag(tagNode, writer, false);
|
||||
|
||||
if (!isMinimizedTagSyntax(tagNode)) {
|
||||
for (final Object item : tagNode.getChildren()) {
|
||||
if (item instanceof ContentNode) {
|
||||
final String content = item.toString();
|
||||
writer.write(dontEscape(tagNode) ? content.replaceAll("]]>", "]]>") : escapeXml(content));
|
||||
} else if (item instanceof BaseToken) {
|
||||
((BaseToken) item).serialize(this, writer);
|
||||
}
|
||||
}
|
||||
|
||||
serializeEndTag(tagNode, writer, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This class contains map with special entities used in HTML and their
|
||||
* unicodes.
|
||||
* </p>
|
||||
*/
|
||||
final public class SpecialEntity {
|
||||
|
||||
private static Map<String, SpecialEntity> entities = new HashMap<String, SpecialEntity>();
|
||||
|
||||
private static int maxEntityLength = 0;
|
||||
|
||||
static {
|
||||
addEntity("nbsp", 160);
|
||||
addEntity("iexcl", 161);
|
||||
addEntity("cent", 162);
|
||||
addEntity("pound", 163);
|
||||
addEntity("curren", 164);
|
||||
addEntity("yen", 165);
|
||||
addEntity("brvbar", 166);
|
||||
addEntity("sect", 167);
|
||||
addEntity("uml", 168);
|
||||
addEntity("copy", 169);
|
||||
addEntity("ordf", 170);
|
||||
addEntity("laquo", 171);
|
||||
addEntity("not", 172);
|
||||
addEntity("shy", 173);
|
||||
addEntity("reg", 174);
|
||||
addEntity("macr", 175);
|
||||
addEntity("deg", 176);
|
||||
addEntity("plusmn", 177);
|
||||
addEntity("sup2", 178);
|
||||
addEntity("sup3", 179);
|
||||
addEntity("acute", 180);
|
||||
addEntity("micro", 181);
|
||||
addEntity("para", 182);
|
||||
addEntity("middot", 183);
|
||||
addEntity("cedil", 184);
|
||||
addEntity("sup1", 185);
|
||||
addEntity("ordm", 186);
|
||||
addEntity("raquo", 187);
|
||||
addEntity("frac14", 188);
|
||||
addEntity("frac12", 189);
|
||||
addEntity("frac34", 190);
|
||||
addEntity("iquest", 191);
|
||||
addEntity("Agrave", 192);
|
||||
addEntity("Aacute", 193);
|
||||
addEntity("Acirc", 194);
|
||||
addEntity("Atilde", 195);
|
||||
addEntity("Auml", 196);
|
||||
addEntity("Aring", 197);
|
||||
addEntity("AElig", 198);
|
||||
addEntity("Ccedil", 199);
|
||||
addEntity("Egrave", 200);
|
||||
addEntity("Eacute", 201);
|
||||
addEntity("Ecirc", 202);
|
||||
addEntity("Euml", 203);
|
||||
addEntity("Igrave", 204);
|
||||
addEntity("Iacute", 205);
|
||||
addEntity("Icirc", 206);
|
||||
addEntity("Iuml", 207);
|
||||
addEntity("ETH", 208);
|
||||
addEntity("Ntilde", 209);
|
||||
addEntity("Ograve", 210);
|
||||
addEntity("Oacute", 211);
|
||||
addEntity("Ocirc", 212);
|
||||
addEntity("Otilde", 213);
|
||||
addEntity("Ouml", 214);
|
||||
addEntity("times", 215);
|
||||
addEntity("Oslash", 216);
|
||||
addEntity("Ugrave", 217);
|
||||
addEntity("Uacute", 218);
|
||||
addEntity("Ucirc", 219);
|
||||
addEntity("Uuml", 220);
|
||||
addEntity("Yacute", 221);
|
||||
addEntity("THORN", 222);
|
||||
addEntity("szlig", 223);
|
||||
addEntity("agrave", 224);
|
||||
addEntity("aacute", 225);
|
||||
addEntity("acirc", 226);
|
||||
addEntity("atilde", 227);
|
||||
addEntity("auml", 228);
|
||||
addEntity("aring", 229);
|
||||
addEntity("aelig", 230);
|
||||
addEntity("ccedil", 231);
|
||||
addEntity("egrave", 232);
|
||||
addEntity("eacute", 233);
|
||||
addEntity("ecirc", 234);
|
||||
addEntity("euml", 235);
|
||||
addEntity("igrave", 236);
|
||||
addEntity("iacute", 237);
|
||||
addEntity("icirc", 238);
|
||||
addEntity("iuml", 239);
|
||||
addEntity("eth", 240);
|
||||
addEntity("ntilde", 241);
|
||||
addEntity("ograve", 242);
|
||||
addEntity("oacute", 243);
|
||||
addEntity("ocirc", 244);
|
||||
addEntity("otilde", 245);
|
||||
addEntity("ouml", 246);
|
||||
addEntity("divide", 247);
|
||||
addEntity("oslash", 248);
|
||||
addEntity("ugrave", 249);
|
||||
addEntity("uacute", 250);
|
||||
addEntity("ucirc", 251);
|
||||
addEntity("uuml", 252);
|
||||
addEntity("yacute", 253);
|
||||
addEntity("thorn", 254);
|
||||
addEntity("yuml", 255);
|
||||
addEntity("OElig", 338);
|
||||
addEntity("oelig", 339);
|
||||
addEntity("Scaron", 352);
|
||||
addEntity("scaron", 353);
|
||||
addEntity("Yuml", 376);
|
||||
addEntity("fnof", 402);
|
||||
addEntity("circ", 710);
|
||||
addEntity("tilde", 732);
|
||||
|
||||
// Greek letters
|
||||
addEntity("Alpha", 913);
|
||||
addEntity("Beta", 914);
|
||||
addEntity("Gamma", 915);
|
||||
addEntity("Delta", 916);
|
||||
addEntity("Epsilon", 917);
|
||||
addEntity("Zeta", 918);
|
||||
addEntity("Eta", 919);
|
||||
addEntity("Theta", 920);
|
||||
addEntity("Iota", 921);
|
||||
addEntity("Kappa", 922);
|
||||
addEntity("Lambda", 923);
|
||||
addEntity("Mu", 924);
|
||||
addEntity("Nu", 925);
|
||||
addEntity("Xi", 926);
|
||||
addEntity("Omicron", 927);
|
||||
addEntity("Pi", 928);
|
||||
addEntity("Rho", 929);
|
||||
addEntity("Sigma", 931);
|
||||
addEntity("Tau", 932);
|
||||
addEntity("Upsilon", 933);
|
||||
addEntity("Phi", 934);
|
||||
addEntity("Chi", 935);
|
||||
addEntity("Psi", 936);
|
||||
addEntity("Omega", 937);
|
||||
addEntity("alpha", 945);
|
||||
addEntity("beta", 946);
|
||||
addEntity("gamma", 947);
|
||||
addEntity("delta", 948);
|
||||
addEntity("epsilon", 949);
|
||||
addEntity("zeta", 950);
|
||||
addEntity("eta", 951);
|
||||
addEntity("theta", 952);
|
||||
addEntity("iota", 953);
|
||||
addEntity("kappa", 954);
|
||||
addEntity("lambda", 955);
|
||||
addEntity("mu", 956);
|
||||
addEntity("nu", 957);
|
||||
addEntity("xi", 958);
|
||||
addEntity("omicron", 959);
|
||||
addEntity("pi", 960);
|
||||
addEntity("rho", 961);
|
||||
addEntity("sigmaf", 962);
|
||||
addEntity("sigma", 963);
|
||||
addEntity("tau", 964);
|
||||
addEntity("upsilon", 965);
|
||||
addEntity("phi", 966);
|
||||
addEntity("chi", 967);
|
||||
addEntity("psi", 968);
|
||||
addEntity("omega", 969);
|
||||
addEntity("thetasym", 977);
|
||||
addEntity("upsih", 978);
|
||||
addEntity("piv", 982);
|
||||
|
||||
addEntity("ensp", 8194);
|
||||
addEntity("emsp", 8195);
|
||||
addEntity("thinsp", 8201);
|
||||
addEntity("zwnj", 8204);
|
||||
addEntity("zwj", 8205);
|
||||
addEntity("lrm", 8206);
|
||||
addEntity("rlm", 8207);
|
||||
addEntity("ndash", 8211);
|
||||
addEntity("mdash", 8212);
|
||||
addEntity("lsquo", 8216);
|
||||
addEntity("rsquo", 8217);
|
||||
addEntity("sbquo", 8218);
|
||||
addEntity("ldquo", 8220);
|
||||
addEntity("rdquo", 8221);
|
||||
addEntity("bdquo", 8222);
|
||||
addEntity("dagger", 8224);
|
||||
addEntity("Dagger", 8225);
|
||||
addEntity("bull", 8226);
|
||||
|
||||
addEntity("hellip", 8230);
|
||||
addEntity("permil", 8240);
|
||||
addEntity("prime", 8242);
|
||||
addEntity("Prime", 8243);
|
||||
addEntity("lsaquo", 8249);
|
||||
addEntity("rsaquo", 8250);
|
||||
addEntity("oline", 8254);
|
||||
addEntity("frasl", 8260);
|
||||
addEntity("euro", 8364);
|
||||
addEntity("image", 8465);
|
||||
addEntity("weierp", 8472);
|
||||
addEntity("real", 8476);
|
||||
addEntity("trade", 8482);
|
||||
addEntity("alefsym", 8501);
|
||||
addEntity("larr", 8592);
|
||||
addEntity("uarr", 8593);
|
||||
addEntity("rarr", 8594);
|
||||
addEntity("darr", 8595);
|
||||
addEntity("harr", 8596);
|
||||
addEntity("crarr", 8629);
|
||||
addEntity("lArr", 8656);
|
||||
addEntity("uArr", 8657);
|
||||
addEntity("rArr", 8658);
|
||||
addEntity("dArr", 8659);
|
||||
addEntity("hArr", 8660);
|
||||
|
||||
// math symbols
|
||||
addEntity("forall", 8704);
|
||||
addEntity("part", 8706);
|
||||
addEntity("exist", 8707);
|
||||
addEntity("empty", 8709);
|
||||
addEntity("nabla", 8711);
|
||||
addEntity("isin", 8712);
|
||||
addEntity("notin", 8713);
|
||||
addEntity("ni", 8715);
|
||||
addEntity("prod", 8719);
|
||||
addEntity("sum", 8721);
|
||||
addEntity("minus", 8722);
|
||||
addEntity("lowast", 8727);
|
||||
addEntity("radic", 8730);
|
||||
addEntity("prop", 8733);
|
||||
addEntity("infin", 8734);
|
||||
addEntity("ang", 8736);
|
||||
addEntity("and", 8743);
|
||||
addEntity("or", 8744);
|
||||
addEntity("cap", 8745);
|
||||
addEntity("cup", 8746);
|
||||
addEntity("int", 8747);
|
||||
addEntity("there4", 8756);
|
||||
addEntity("sim", 8764);
|
||||
addEntity("cong", 8773);
|
||||
addEntity("asymp", 8776);
|
||||
addEntity("ne", 8800);
|
||||
addEntity("equiv", 8801);
|
||||
addEntity("le", 8804);
|
||||
addEntity("ge", 8805);
|
||||
addEntity("sub", 8834);
|
||||
addEntity("sup", 8835);
|
||||
addEntity("nsub", 8836);
|
||||
addEntity("sube", 8838);
|
||||
addEntity("supe", 8839);
|
||||
addEntity("oplus", 8853);
|
||||
addEntity("otimes", 8855);
|
||||
addEntity("perp", 8869);
|
||||
addEntity("sdot", 8901);
|
||||
addEntity("lceil", 8968);
|
||||
addEntity("rceil", 8969);
|
||||
addEntity("lfloor", 8970);
|
||||
addEntity("rfloor", 8971);
|
||||
addEntity("lang", 9001);
|
||||
addEntity("rang", 9002);
|
||||
addEntity("loz", 9674);
|
||||
addEntity("spades", 9824);
|
||||
addEntity("clubs", 9827);
|
||||
addEntity("hearts", 9829);
|
||||
addEntity("diams", 9830);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new entity to the set.
|
||||
*
|
||||
* @param entityName
|
||||
* Entity name, for example "pound"
|
||||
* @param intCode
|
||||
* Unicode of the entity, for example 163
|
||||
*
|
||||
* @throws org.htmlcleaner.HtmlCleanerException
|
||||
*/
|
||||
public static void addEntity(final String entityName, final int intCode) throws HtmlCleanerException {
|
||||
if (entities.containsKey(entityName)) {
|
||||
throw new HtmlCleanerException("Entity \"" + entityName + "\" is already defined!");
|
||||
}
|
||||
entities.put(entityName, new SpecialEntity(entityName, intCode));
|
||||
final int entityNameLen = entityName.length();
|
||||
if (entityNameLen > maxEntityLength) {
|
||||
maxEntityLength = entityNameLen;
|
||||
}
|
||||
}
|
||||
|
||||
public static SpecialEntity getEntity(final String key) {
|
||||
return entities.get(key);
|
||||
}
|
||||
|
||||
public static int getMaxEntityLength() {
|
||||
return maxEntityLength;
|
||||
}
|
||||
|
||||
final private String key;
|
||||
final private int intCode;
|
||||
|
||||
private SpecialEntity(final String key, final int intCode) {
|
||||
this.key = key;
|
||||
this.intCode = intCode;
|
||||
}
|
||||
|
||||
public char getCharacter() {
|
||||
return (char) intCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Numeric Character Reference in decimal format
|
||||
*/
|
||||
public String getDecimalNCR() {
|
||||
return "&#" + intCode + ";";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Escaped value of the entity
|
||||
*/
|
||||
public String getEscapedValue() {
|
||||
return "&" + key + ";";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Numeric Character Reference in hex format
|
||||
*/
|
||||
public String getHexNCR() {
|
||||
return "&#x" + Integer.toHexString(intCode) + ";";
|
||||
}
|
||||
|
||||
public int getIntCode() {
|
||||
return intCode;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class contains information about single HTML tag.<br/>
|
||||
* It also contains rules for tag balancing. For each tag, list of dependant
|
||||
* tags may be defined. There are several kinds of dependancies used to reorder
|
||||
* tags:
|
||||
* <ul>
|
||||
* <li>
|
||||
* fatal tags - required outer tag - the tag will be ignored during parsing
|
||||
* (will be skipped) if this fatal tag is missing. For example, most web
|
||||
* browsers ignore elements TD, TR, TBODY if they are not in the context of
|
||||
* TABLE tag.</li>
|
||||
* <li>
|
||||
* required enclosing tags - if there is no such, it is implicitely created. For
|
||||
* example if TD is out of TR - open TR is created before.</li>
|
||||
* <li>
|
||||
* forbidden tags - it is not allowed to occure inside - for example FORM cannot
|
||||
* be inside other FORM and it will be ignored during cleanup.</li>
|
||||
* <li>
|
||||
* allowed children tags - for example TR allowes TD and TH. If there are some
|
||||
* dependant allowed tags defined then cleaner ignores other tags, treating them
|
||||
* as unallowed, unless they are in some other relationship with this tag.</li>
|
||||
* <li>
|
||||
* higher level tags - for example for TR higher tags are THEAD, TBODY, TFOOT.</li>
|
||||
* <li>
|
||||
* tags that must be closed and copied - for example, in
|
||||
* <code><a href="#"><div>....</code> tag A must be closed before
|
||||
* DIV but copied again inside DIV.</li>
|
||||
* <li>
|
||||
* tags that must be closed before closing this tag and copied again after - for
|
||||
* example, in <code><i><b>at</i> first</b> text </code>
|
||||
* tag B must be closed before closing I, but it must be copied again after
|
||||
* resulting finally in sequence:
|
||||
* <code><i><b>at</b></i><b> first</b> text </code>
|
||||
* .</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Tag TR for instance (table row) may define the following dependancies:
|
||||
* <ul>
|
||||
* <li>fatal tag is <code>table</code></li>
|
||||
* <li>required enclosing tag is <code>tbody</code></li>
|
||||
* <li>allowed children tags are <code>td,th</code></li>
|
||||
* <li>higher level tags are <code>thead,tfoot</code></li>
|
||||
* <li>tags that muste be closed before are
|
||||
* <code>tr,td,th,caption,colgroup</code></li>
|
||||
* </ul>
|
||||
* meaning the following: <br>
|
||||
* <ul>
|
||||
* <li><code>tr</code> must be in context of <code>table</code>, otherwise it
|
||||
* will be ignored,</li>
|
||||
* <li><code>tr</code> may can be directly inside <code>tbody</code>,
|
||||
* <code>tfoot</code> and <code>thead</code>, otherwise <code>tbody</code> will
|
||||
* be implicitely created in front of it.</li>
|
||||
* <li><code>tr</code> can contain <code>td</code> and <code>th</code>, all
|
||||
* other tags and content will be pushed out of current limiting context, in the
|
||||
* case of html tables, in front of enclosing <code>table</code> tag.</li>
|
||||
* <li>if previous open tag is one of <code>tr</code>, <code>caption</code> or
|
||||
* <code>colgroup</code>, it will be implicitely closed.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public class TagInfo {
|
||||
|
||||
protected static final int HEAD_AND_BODY = 0;
|
||||
protected static final int HEAD = 1;
|
||||
protected static final int BODY = 2;
|
||||
|
||||
protected static final int CONTENT_ALL = 0;
|
||||
protected static final int CONTENT_NONE = 1;
|
||||
protected static final int CONTENT_TEXT = 2;
|
||||
|
||||
private String name;
|
||||
final private int contentType;
|
||||
private Set mustCloseTags = new HashSet();
|
||||
private Set higherTags = new HashSet();
|
||||
private Set childTags = new HashSet();
|
||||
private Set permittedTags = new HashSet();
|
||||
private Set copyTags = new HashSet();
|
||||
private Set continueAfterTags = new HashSet();
|
||||
private int belongsTo = BODY;
|
||||
private String requiredParent = null;
|
||||
private String fatalTag = null;
|
||||
private boolean deprecated = false;
|
||||
private boolean unique = false;
|
||||
private boolean ignorePermitted = false;
|
||||
|
||||
public TagInfo(final String name, final int contentType, final int belongsTo, final boolean depricated,
|
||||
final boolean unique, final boolean ignorePermitted) {
|
||||
this.name = name;
|
||||
this.contentType = contentType;
|
||||
this.belongsTo = belongsTo;
|
||||
this.deprecated = depricated;
|
||||
this.unique = unique;
|
||||
this.ignorePermitted = ignorePermitted;
|
||||
}
|
||||
|
||||
public boolean allowsAnything() {
|
||||
return CONTENT_ALL == contentType && childTags.isEmpty();
|
||||
}
|
||||
|
||||
public boolean allowsBody() {
|
||||
return CONTENT_NONE != contentType;
|
||||
}
|
||||
|
||||
public boolean allowsItem(final BaseToken token) {
|
||||
if (contentType != CONTENT_NONE && token instanceof TagToken) {
|
||||
final TagToken tagToken = (TagToken) token;
|
||||
final String tagName = tagToken.getName();
|
||||
if ("script".equals(tagName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CONTENT_ALL == contentType) {
|
||||
if (!childTags.isEmpty()) {
|
||||
return token instanceof TagToken ? childTags.contains(((TagToken) token).getName()) : false;
|
||||
} else if (!permittedTags.isEmpty()) {
|
||||
return token instanceof TagToken ? !permittedTags.contains(((TagToken) token).getName()) : true;
|
||||
}
|
||||
return true;
|
||||
} else if (CONTENT_TEXT == contentType) {
|
||||
return !(token instanceof TagToken);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void defineAllowedChildrenTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.childTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineCloseBeforeCopyInsideTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.copyTags.add(currTag);
|
||||
this.mustCloseTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineCloseBeforeTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.mustCloseTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineCloseInsideCopyAfterTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.continueAfterTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineFatalTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.fatalTag = currTag;
|
||||
this.higherTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
|
||||
public void defineForbiddenTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.permittedTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineHigherLevelTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.higherTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public void defineRequiredEnclosingTags(final String commaSeparatedListOfTags) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(commaSeparatedListOfTags.toLowerCase(), ",");
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
final String currTag = tokenizer.nextToken();
|
||||
this.requiredParent = currTag;
|
||||
this.higherTags.add(currTag);
|
||||
}
|
||||
}
|
||||
|
||||
public int getBelongsTo() {
|
||||
return belongsTo;
|
||||
}
|
||||
|
||||
public Set getChildTags() {
|
||||
return childTags;
|
||||
}
|
||||
|
||||
public int getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public Set getContinueAfterTags() {
|
||||
return continueAfterTags;
|
||||
}
|
||||
|
||||
public Set getCopyTags() {
|
||||
return copyTags;
|
||||
}
|
||||
|
||||
public String getFatalTag() {
|
||||
return fatalTag;
|
||||
}
|
||||
|
||||
public Set getHigherTags() {
|
||||
return higherTags;
|
||||
}
|
||||
|
||||
public Set getMustCloseTags() {
|
||||
return mustCloseTags;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Set getPermittedTags() {
|
||||
return permittedTags;
|
||||
}
|
||||
|
||||
public String getRequiredParent() {
|
||||
return requiredParent;
|
||||
}
|
||||
|
||||
public boolean hasCopyTags() {
|
||||
return !copyTags.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasPermittedTags() {
|
||||
return !permittedTags.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isContinueAfter(final String tagName) {
|
||||
return continueAfterTags.contains(tagName);
|
||||
}
|
||||
|
||||
public boolean isCopy(final String tagName) {
|
||||
return copyTags.contains(tagName);
|
||||
}
|
||||
|
||||
public boolean isDeprecated() {
|
||||
return deprecated;
|
||||
}
|
||||
|
||||
public boolean isEmptyTag() {
|
||||
return CONTENT_NONE == contentType;
|
||||
}
|
||||
|
||||
public boolean isHeadAndBodyTag() {
|
||||
return belongsTo == HEAD || belongsTo == HEAD_AND_BODY;
|
||||
}
|
||||
|
||||
public boolean isHeadTag() {
|
||||
return belongsTo == HEAD;
|
||||
}
|
||||
|
||||
public boolean isHigher(final String tagName) {
|
||||
return higherTags.contains(tagName);
|
||||
}
|
||||
|
||||
public boolean isIgnorePermitted() {
|
||||
return ignorePermitted;
|
||||
}
|
||||
|
||||
public boolean isMustCloseTag(final TagInfo tagInfo) {
|
||||
if (tagInfo != null) {
|
||||
return mustCloseTags.contains(tagInfo.getName()) || tagInfo.contentType == CONTENT_TEXT;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isUnique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
public void setBelongsTo(final int belongsTo) {
|
||||
this.belongsTo = belongsTo;
|
||||
}
|
||||
|
||||
public void setChildTags(final Set childTags) {
|
||||
this.childTags = childTags;
|
||||
}
|
||||
|
||||
// other functionality
|
||||
|
||||
public void setContinueAfterTags(final Set continueAfterTags) {
|
||||
this.continueAfterTags = continueAfterTags;
|
||||
}
|
||||
|
||||
public void setCopyTags(final Set copyTags) {
|
||||
this.copyTags = copyTags;
|
||||
}
|
||||
|
||||
public void setDeprecated(final boolean deprecated) {
|
||||
this.deprecated = deprecated;
|
||||
}
|
||||
|
||||
public void setFatalTag(final String fatalTag) {
|
||||
this.fatalTag = fatalTag;
|
||||
}
|
||||
|
||||
public void setHigherTags(final Set higherTags) {
|
||||
this.higherTags = higherTags;
|
||||
}
|
||||
|
||||
public void setIgnorePermitted(final boolean ignorePermitted) {
|
||||
this.ignorePermitted = ignorePermitted;
|
||||
}
|
||||
|
||||
public void setMustCloseTags(final Set mustCloseTags) {
|
||||
this.mustCloseTags = mustCloseTags;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setPermittedTags(final Set permittedTags) {
|
||||
this.permittedTags = permittedTags;
|
||||
}
|
||||
|
||||
public void setRequiredParent(final String requiredParent) {
|
||||
this.requiredParent = requiredParent;
|
||||
}
|
||||
|
||||
public void setUnique(final boolean unique) {
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,765 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* XML node tag - basic node of the cleaned HTML tree. At the same time, it
|
||||
* represents start tag token after HTML parsing phase and before cleaning
|
||||
* phase. After cleaning process, tree structure remains containing tag nodes
|
||||
* (TagNode class), content (text nodes - ContentNode), comments (CommentNode)
|
||||
* and optionally doctype node (DoctypeToken).
|
||||
* </p>
|
||||
*/
|
||||
public class TagNode extends TagToken implements HtmlNode {
|
||||
|
||||
/**
|
||||
* Used as base for different node checkers.
|
||||
*/
|
||||
public interface ITagNodeCondition {
|
||||
boolean satisfy(TagNode tagNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* All nodes.
|
||||
*/
|
||||
public class TagAllCondition implements ITagNodeCondition {
|
||||
public boolean satisfy(final TagNode tagNode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node contains specified attribute.
|
||||
*/
|
||||
public class TagNodeAttExistsCondition implements ITagNodeCondition {
|
||||
final private String attName;
|
||||
|
||||
public TagNodeAttExistsCondition(final String attName) {
|
||||
this.attName = attName.toLowerCase();
|
||||
}
|
||||
|
||||
public boolean satisfy(final TagNode tagNode) {
|
||||
return tagNode == null ? false : tagNode.attributes.containsKey(attName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node has specified attribute with specified value.
|
||||
*/
|
||||
public class TagNodeAttValueCondition implements ITagNodeCondition {
|
||||
final private String attName;
|
||||
final private String attValue;
|
||||
final private boolean isCaseSensitive;
|
||||
|
||||
public TagNodeAttValueCondition(final String attName, final String attValue, final boolean isCaseSensitive) {
|
||||
this.attName = attName;
|
||||
this.attValue = attValue;
|
||||
this.isCaseSensitive = isCaseSensitive;
|
||||
}
|
||||
|
||||
public boolean satisfy(final TagNode tagNode) {
|
||||
if (tagNode == null || attName == null || attValue == null) {
|
||||
return false;
|
||||
} else {
|
||||
return isCaseSensitive ? attValue.equals(tagNode.getAttributeByName(attName)) : attValue
|
||||
.equalsIgnoreCase(tagNode.getAttributeByName(attName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node has specified name.
|
||||
*/
|
||||
public class TagNodeNameCondition implements ITagNodeCondition {
|
||||
final private String name;
|
||||
|
||||
public TagNodeNameCondition(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean satisfy(final TagNode tagNode) {
|
||||
return tagNode == null ? false : tagNode.name.equalsIgnoreCase(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
private TagNode parent = null;
|
||||
private Map<String, String> attributes = new LinkedHashMap<String, String>();
|
||||
private List<Object> children = new ArrayList<Object>();
|
||||
private DoctypeToken docType = null;
|
||||
private Map<String, String> nsDeclarations = null;
|
||||
private List<BaseToken> itemsToMove = null;
|
||||
|
||||
private transient boolean isFormed = false;
|
||||
|
||||
public TagNode(final String name) {
|
||||
super(name == null ? null : name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use setAttribute instead Adds specified attribute to this tag
|
||||
* or overrides existing one.
|
||||
* @param attName
|
||||
* @param attValue
|
||||
*/
|
||||
@Deprecated
|
||||
public void addAttribute(final String attName, final String attValue) {
|
||||
setAttribute(attName, attValue);
|
||||
}
|
||||
|
||||
public void addChild(final Object child) {
|
||||
if (child == null) {
|
||||
return;
|
||||
}
|
||||
if (child instanceof List<?>) {
|
||||
addChildren((List<?>) child);
|
||||
} else {
|
||||
children.add(child);
|
||||
if (child instanceof TagNode) {
|
||||
final TagNode childTagNode = (TagNode) child;
|
||||
childTagNode.parent = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all elements from specified list to this node.
|
||||
*
|
||||
* @param newChildren
|
||||
*/
|
||||
public void addChildren(final List<?> newChildren) {
|
||||
if (newChildren != null) {
|
||||
final Iterator<?> it = newChildren.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Object child = it.next();
|
||||
addChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addItemForMoving(final BaseToken item) {
|
||||
if (itemsToMove == null) {
|
||||
itemsToMove = new ArrayList<BaseToken>();
|
||||
}
|
||||
|
||||
itemsToMove.add(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds namespace declaration to the node
|
||||
*
|
||||
* @param nsPrefix
|
||||
* Namespace prefix
|
||||
* @param nsURI
|
||||
* Namespace URI
|
||||
*/
|
||||
public void addNamespaceDeclaration(final String nsPrefix, final String nsURI) {
|
||||
if (nsDeclarations == null) {
|
||||
nsDeclarations = new TreeMap<String, String>();
|
||||
}
|
||||
nsDeclarations.put(nsPrefix, nsURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all prefixes in namespace declarations up the path to the
|
||||
* document root from the specified node
|
||||
*
|
||||
* @param prefixes
|
||||
* Set of prefixes to be collected
|
||||
*/
|
||||
public void collectNamespacePrefixesOnPath(final Set<String> prefixes) {
|
||||
final Map<String, String> nsDeclarations = getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (String prefix : nsDeclarations.keySet()) {
|
||||
prefixes.add(prefix);
|
||||
}
|
||||
}
|
||||
if (parent != null) {
|
||||
parent.collectNamespacePrefixesOnPath(prefixes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates XPath expression on give node. <br>
|
||||
* <em>
|
||||
* This is not fully supported XPath parser and evaluator.
|
||||
* Examples below show supported elements:
|
||||
* </em> <code>
|
||||
* <ul>
|
||||
* <li>//div//a</li>
|
||||
* <li>//div//a[@id][@class]</li>
|
||||
* <li>/body/*[1]/@type</li>
|
||||
* <li>//div[3]//a[@id][@href='r/n4']</li>
|
||||
* <li>//div[last() >= 4]//./div[position() = last()])[position() > 22]//li[2]//a</li>
|
||||
* <li>//div[2]/@*[2]</li>
|
||||
* <li>data(//div//a[@id][@class])</li>
|
||||
* <li>//p/last()</li>
|
||||
* <li>//body//div[3][@class]//span[12.2<position()]/@id</li>
|
||||
* <li>data(//a['v' < @id])</li>
|
||||
* </ul>
|
||||
* </code>
|
||||
*
|
||||
* @param xPathExpression
|
||||
* @return
|
||||
* @throws XPatherException
|
||||
*/
|
||||
public Object[] evaluateXPath(final String xPathExpression) throws XPatherException {
|
||||
return new XPather(xPathExpression).evaluateAgainstNode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first element in the tree that satisfy specified condition.
|
||||
*
|
||||
* @param condition
|
||||
* @param isRecursive
|
||||
* @return First TagNode found, or null if no such elements.
|
||||
*/
|
||||
private TagNode findElement(final ITagNodeCondition condition, final boolean isRecursive) {
|
||||
if (condition == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
final Object item = children.get(i);
|
||||
if (item instanceof TagNode) {
|
||||
final TagNode currNode = (TagNode) item;
|
||||
if (condition.satisfy(currNode)) {
|
||||
return currNode;
|
||||
} else if (isRecursive) {
|
||||
final TagNode inner = currNode.findElement(condition, isRecursive);
|
||||
if (inner != null) {
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TagNode findElementByAttValue(final String attName, final String attValue, final boolean isRecursive,
|
||||
final boolean isCaseSensitive) {
|
||||
return findElement(new TagNodeAttValueCondition(attName, attValue, isCaseSensitive), isRecursive);
|
||||
}
|
||||
|
||||
public TagNode findElementByName(final String findName, final boolean isRecursive) {
|
||||
return findElement(new TagNodeNameCondition(findName), isRecursive);
|
||||
}
|
||||
|
||||
public TagNode findElementHavingAttribute(final String attName, final boolean isRecursive) {
|
||||
return findElement(new TagNodeAttExistsCondition(attName), isRecursive);
|
||||
}
|
||||
|
||||
public TagNode[] getAllElements(final boolean isRecursive) {
|
||||
return getElements(new TagAllCondition(), isRecursive);
|
||||
}
|
||||
|
||||
public List getAllElementsList(final boolean isRecursive) {
|
||||
return getElementList(new TagAllCondition(), isRecursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attName
|
||||
* @return Value of the specified attribute, or null if it this tag doesn't
|
||||
* contain it.
|
||||
*/
|
||||
public String getAttributeByName(final String attName) {
|
||||
return attName != null ? attributes.get(attName.toLowerCase()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map instance containing all attribute name/value pairs.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param child
|
||||
* Child to find index of
|
||||
* @return Index of the specified child node inside this node's children, -1
|
||||
* if node is not the child
|
||||
*/
|
||||
public int getChildIndex(final HtmlNode child) {
|
||||
int index = 0;
|
||||
for (final Object curr : children) {
|
||||
if (curr == child) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of children objects. During the cleanup process there could
|
||||
* be different kind of childern inside, however after clean there
|
||||
* should be only TagNode instances.
|
||||
*/
|
||||
public List<Object> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public List<TagNode> getChildTagList() {
|
||||
final List<TagNode> childTagList = new ArrayList<TagNode>();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
final Object item = children.get(i);
|
||||
if (item instanceof TagNode) {
|
||||
childTagList.add((TagNode) item);
|
||||
}
|
||||
}
|
||||
|
||||
return childTagList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An array of child TagNode instances.
|
||||
*/
|
||||
public TagNode[] getChildTags() {
|
||||
final List<TagNode> childTagList = getChildTagList();
|
||||
final TagNode childrenArray[] = new TagNode[childTagList.size()];
|
||||
for (int i = 0; i < childTagList.size(); i++) {
|
||||
childrenArray[i] = (TagNode) childTagList.get(i);
|
||||
}
|
||||
|
||||
return childrenArray;
|
||||
}
|
||||
|
||||
public DoctypeToken getDocType() {
|
||||
return docType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in the tree that satisfy specified condition.
|
||||
*
|
||||
* @param condition
|
||||
* @param isRecursive
|
||||
* @return List of TagNode instances with specified name.
|
||||
*/
|
||||
private List getElementList(final ITagNodeCondition condition, final boolean isRecursive) {
|
||||
final List result = new LinkedList();
|
||||
if (condition == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
final Object item = children.get(i);
|
||||
if (item instanceof TagNode) {
|
||||
final TagNode currNode = (TagNode) item;
|
||||
if (condition.satisfy(currNode)) {
|
||||
result.add(currNode);
|
||||
}
|
||||
if (isRecursive) {
|
||||
final List innerList = currNode.getElementList(condition, isRecursive);
|
||||
if (innerList != null && !innerList.isEmpty()) {
|
||||
result.addAll(innerList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List getElementListByAttValue(final String attName, final String attValue, final boolean isRecursive,
|
||||
final boolean isCaseSensitive) {
|
||||
return getElementList(new TagNodeAttValueCondition(attName, attValue, isCaseSensitive), isRecursive);
|
||||
}
|
||||
|
||||
public List getElementListByName(final String findName, final boolean isRecursive) {
|
||||
return getElementList(new TagNodeNameCondition(findName), isRecursive);
|
||||
}
|
||||
|
||||
public List getElementListHavingAttribute(final String attName, final boolean isRecursive) {
|
||||
return getElementList(new TagNodeAttExistsCondition(attName), isRecursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param condition
|
||||
* @param isRecursive
|
||||
* @return The array of all subelemets that satisfy specified condition.
|
||||
*/
|
||||
private TagNode[] getElements(final ITagNodeCondition condition, final boolean isRecursive) {
|
||||
final List list = getElementList(condition, isRecursive);
|
||||
final TagNode array[] = new TagNode[list == null ? 0 : list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = (TagNode) list.get(i);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public TagNode[] getElementsByAttValue(final String attName, final String attValue, final boolean isRecursive,
|
||||
final boolean isCaseSensitive) {
|
||||
return getElements(new TagNodeAttValueCondition(attName, attValue, isCaseSensitive), isRecursive);
|
||||
}
|
||||
|
||||
public TagNode[] getElementsByName(final String findName, final boolean isRecursive) {
|
||||
return getElements(new TagNodeNameCondition(findName), isRecursive);
|
||||
}
|
||||
|
||||
public TagNode[] getElementsHavingAttribute(final String attName, final boolean isRecursive) {
|
||||
return getElements(new TagNodeAttExistsCondition(attName), isRecursive);
|
||||
}
|
||||
|
||||
public List<BaseToken> getItemsToMove() {
|
||||
return itemsToMove;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map of namespace declarations for this node
|
||||
*/
|
||||
public Map<String, String> getNamespaceDeclarations() {
|
||||
return nsDeclarations;
|
||||
}
|
||||
|
||||
public String getNamespaceURIOnPath(final String nsPrefix) {
|
||||
if (nsDeclarations != null) {
|
||||
for (final Map.Entry<String, String> nsEntry : nsDeclarations.entrySet()) {
|
||||
final String currName = nsEntry.getKey();
|
||||
if (currName.equals(nsPrefix) || ("".equals(currName) && nsPrefix == null)) {
|
||||
return nsEntry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent != null) {
|
||||
return parent.getNamespaceURIOnPath(nsPrefix);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Parent of this node, or null if this is the root node.
|
||||
*/
|
||||
public TagNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Text content of this node and it's subelements.
|
||||
*/
|
||||
public StringBuffer getText() {
|
||||
final StringBuffer text = new StringBuffer();
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
final Object item = children.get(i);
|
||||
if (item instanceof ContentNode) {
|
||||
text.append(item.toString());
|
||||
} else if (item instanceof TagNode) {
|
||||
final StringBuffer subtext = ((TagNode) item).getText();
|
||||
text.append(subtext);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks existance of specified attribute.
|
||||
*
|
||||
* @param attName
|
||||
*/
|
||||
public boolean hasAttribute(final String attName) {
|
||||
return attName != null ? attributes.containsKey(attName.toLowerCase()) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether this node has child elements or not.
|
||||
*/
|
||||
public boolean hasChildren() {
|
||||
return !children.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts specified node at specified position in array of children
|
||||
*
|
||||
* @param index
|
||||
* @param childToAdd
|
||||
*/
|
||||
public void insertChild(final int index, final HtmlNode childToAdd) {
|
||||
children.add(index, childToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts specified node in the list of children after specified child
|
||||
*
|
||||
* @param node
|
||||
* Child after which to insert new node
|
||||
* @param nodeToInsert
|
||||
* Node to be inserted at specified position
|
||||
*/
|
||||
public void insertChildAfter(final HtmlNode node, final HtmlNode nodeToInsert) {
|
||||
final int index = getChildIndex(node);
|
||||
if (index >= 0) {
|
||||
insertChild(index + 1, nodeToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts specified node in the list of children before specified child
|
||||
*
|
||||
* @param node
|
||||
* Child before which to insert new node
|
||||
* @param nodeToInsert
|
||||
* Node to be inserted at specified position
|
||||
*/
|
||||
public void insertChildBefore(final HtmlNode node, final HtmlNode nodeToInsert) {
|
||||
final int index = getChildIndex(node);
|
||||
if (index >= 0) {
|
||||
insertChild(index, nodeToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFormed() {
|
||||
return isFormed;
|
||||
}
|
||||
|
||||
public TagNode makeCopy() {
|
||||
final TagNode copy = new TagNode(name);
|
||||
copy.attributes.putAll(attributes);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all children (subelements and text content).
|
||||
*/
|
||||
public void removeAllChildren() {
|
||||
this.children.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified attribute from this tag.
|
||||
*
|
||||
* @param attName
|
||||
*/
|
||||
public void removeAttribute(final String attName) {
|
||||
if (attName != null && !"".equals(attName.trim())) {
|
||||
attributes.remove(attName.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specified child element from this node.
|
||||
*
|
||||
* @param child
|
||||
* @return True if child object existed in the children list.
|
||||
*/
|
||||
public boolean removeChild(final Object child) {
|
||||
return this.children.remove(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this node from the tree.
|
||||
*
|
||||
* @return True if element is removed (if it is not root node).
|
||||
*/
|
||||
public boolean removeFromTree() {
|
||||
if (parent != null) {
|
||||
final boolean existed = parent.removeChild(this);
|
||||
parent = null;
|
||||
return existed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces specified child node with specified replacement node.
|
||||
*
|
||||
* @param childToReplace
|
||||
* Child node to be replaced
|
||||
* @param replacement
|
||||
* Replacement node
|
||||
*/
|
||||
public void replaceChild(final HtmlNode childToReplace, final HtmlNode replacement) {
|
||||
if (replacement == null) {
|
||||
return;
|
||||
}
|
||||
final ListIterator it = children.listIterator();
|
||||
while (it.hasNext()) {
|
||||
final Object curr = it.next();
|
||||
if (curr == childToReplace) {
|
||||
it.set(replacement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void serialize(final Serializer serializer, final Writer writer) throws IOException {
|
||||
serializer.serialize(this, writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding new attribute ir overriding existing one.
|
||||
*
|
||||
* @param attName
|
||||
* @param attValue
|
||||
*/
|
||||
public void setAttribute(String attName, final String attValue) {
|
||||
if (attName != null && !"".equals(attName.trim())) {
|
||||
attName = attName.toLowerCase();
|
||||
if ("xmlns".equals(attName)) {
|
||||
addNamespaceDeclaration("", attValue);
|
||||
} else if (attName.startsWith("xmlns:")) {
|
||||
addNamespaceDeclaration(attName.substring(6), attValue);
|
||||
} else {
|
||||
attributes.put(attName, attValue == null ? "" : attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setChildren(final List<Object> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public void setDocType(final DoctypeToken docType) {
|
||||
this.docType = docType;
|
||||
}
|
||||
|
||||
public void setFormed() {
|
||||
setFormed(true);
|
||||
}
|
||||
|
||||
public void setFormed(final boolean isFormed) {
|
||||
this.isFormed = isFormed;
|
||||
}
|
||||
|
||||
public void setItemsToMove(final List<BaseToken> itemsToMove) {
|
||||
this.itemsToMove = itemsToMove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes name of the tag
|
||||
*
|
||||
* @param name
|
||||
* @return True if new name is valid, false otherwise
|
||||
*/
|
||||
public boolean setName(final String name) {
|
||||
if (Utils.isValidXmlIdentifier(name)) {
|
||||
this.name = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void transformAttributes(final TagTransformation tagTrans) {
|
||||
final boolean isPreserveSourceAtts = tagTrans.isPreserveSourceAttributes();
|
||||
final boolean hasAttTransforms = tagTrans.hasAttributeTransformations();
|
||||
if (hasAttTransforms || !isPreserveSourceAtts) {
|
||||
final Map<String, String> newAttributes = isPreserveSourceAtts ? new LinkedHashMap<String, String>(
|
||||
attributes) : new LinkedHashMap<String, String>();
|
||||
if (hasAttTransforms) {
|
||||
final Map map = tagTrans.getAttributeTransformations();
|
||||
final Iterator iterator = map.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Map.Entry entry = (Map.Entry) iterator.next();
|
||||
final String attName = (String) entry.getKey();
|
||||
final String template = (String) entry.getValue();
|
||||
if (template == null) {
|
||||
newAttributes.remove(attName);
|
||||
} else {
|
||||
final String attValue = Utils.evaluateTemplate(template, attributes);
|
||||
newAttributes.put(attName, attValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.attributes = newAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the tree and performs visitor's action on each node. It stops
|
||||
* when it finishes all the tree or when visitor returns false.
|
||||
*
|
||||
* @param visitor
|
||||
* TagNodeVisitor implementation
|
||||
*/
|
||||
public void traverse(final TagNodeVisitor visitor) {
|
||||
traverseInternally(visitor);
|
||||
}
|
||||
|
||||
private boolean traverseInternally(final TagNodeVisitor visitor) {
|
||||
if (visitor != null) {
|
||||
final boolean hasParent = parent != null;
|
||||
boolean toContinue = visitor.visit(parent, this);
|
||||
|
||||
if (!toContinue) {
|
||||
return false; // if visitor stops traversal
|
||||
} else if (hasParent && parent == null) {
|
||||
return true;
|
||||
// if this node is pruned from the tree during the visit, then
|
||||
// don't go deeper
|
||||
}
|
||||
for (final Object child : children.toArray()) {
|
||||
// make an array to avoid ConcurrentModificationException when
|
||||
// some node is cut
|
||||
if (child instanceof TagNode) {
|
||||
toContinue = ((TagNode) child).traverseInternally(visitor);
|
||||
} else if (child instanceof ContentNode) {
|
||||
toContinue = visitor.visit(this, (ContentNode) child);
|
||||
} else if (child instanceof CommentNode) {
|
||||
toContinue = visitor.visit(this, (CommentNode) child);
|
||||
}
|
||||
if (!toContinue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* Defines action to be performed on TagNodes
|
||||
*/
|
||||
public interface TagNodeVisitor {
|
||||
|
||||
/**
|
||||
* Action to be performed on single node in the tree
|
||||
*
|
||||
* @param parentNode
|
||||
* Parent of tagNode
|
||||
* @param htmlNode
|
||||
* node visited
|
||||
* @return True if tree traversal should be continued, false if it has to
|
||||
* stop.
|
||||
*/
|
||||
boolean visit(TagNode parentNode, HtmlNode htmlNode);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* HTML tag token - descendants are start (TagNode) and end token (EndTagToken).
|
||||
* </p>
|
||||
*/
|
||||
public abstract class TagToken implements BaseToken {
|
||||
protected String name;
|
||||
|
||||
public TagToken() {
|
||||
// TODO empty method
|
||||
}
|
||||
|
||||
public TagToken(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
abstract public void setAttribute(String attName, String attValue);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* Describes how specified tag is transformed to another one, or is ignored
|
||||
* during parsing
|
||||
*/
|
||||
public class TagTransformation {
|
||||
|
||||
private String sourceTag;
|
||||
private String destTag;
|
||||
private boolean preserveSourceAttributes;
|
||||
private Map attributeTransformations;
|
||||
|
||||
/**
|
||||
* Creates new tag transformation from source tag to target tag specifying
|
||||
* whether source tag attributes are preserved.
|
||||
*
|
||||
* @param sourceTag
|
||||
* Name of the tag to be transformed.
|
||||
* @param destTag
|
||||
* Name of tag to which source tag is to be transformed.
|
||||
* @param preserveSourceAttributes
|
||||
* Tells whether source tag attributes are preserved in
|
||||
* transformation.
|
||||
*/
|
||||
public TagTransformation(final String sourceTag, final String destTag, final boolean preserveSourceAttributes) {
|
||||
this.sourceTag = sourceTag.toLowerCase();
|
||||
if (destTag == null) {
|
||||
this.destTag = null;
|
||||
} else {
|
||||
this.destTag = Utils.isValidXmlIdentifier(destTag) ? destTag.toLowerCase() : sourceTag;
|
||||
}
|
||||
this.preserveSourceAttributes = preserveSourceAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new tag transformation from source tag to target tag preserving
|
||||
* all source tag attributes.
|
||||
*
|
||||
* @param sourceTag
|
||||
* Name of the tag to be transformed.
|
||||
* @param destTag
|
||||
* Name of tag to which source tag is to be transformed.
|
||||
*/
|
||||
public TagTransformation(final String sourceTag, final String destTag) {
|
||||
this(sourceTag, destTag, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new tag transformation in which specified tag will be skipped
|
||||
* (ignored) during parsing process.
|
||||
*
|
||||
* @param sourceTag
|
||||
*/
|
||||
public TagTransformation(final String sourceTag) {
|
||||
this(sourceTag, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new attribute transformation to this tag transformation. It tells
|
||||
* how destination attribute will look like. Small templating mechanism is
|
||||
* used to describe attribute value: all names between ${ and } inside the
|
||||
* template are evaluated against source tag attributes. That way one can
|
||||
* make attribute values consist of mix of source tag attributes.
|
||||
*
|
||||
* @param targetAttName
|
||||
* Name of the destination attribute
|
||||
* @param transformationDesc
|
||||
* Template describing attribute value.
|
||||
*/
|
||||
public void addAttributeTransformation(final String targetAttName, final String transformationDesc) {
|
||||
if (attributeTransformations == null) {
|
||||
attributeTransformations = new LinkedHashMap();
|
||||
}
|
||||
attributeTransformations.put(targetAttName.toLowerCase(), transformationDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new attribute transformation in which destination attrbute will not
|
||||
* exists (simply removes it from list of attributes).
|
||||
*
|
||||
* @param targetAttName
|
||||
*/
|
||||
public void addAttributeTransformation(final String targetAttName) {
|
||||
addAttributeTransformation(targetAttName, null);
|
||||
}
|
||||
|
||||
boolean hasAttributeTransformations() {
|
||||
return attributeTransformations != null;
|
||||
}
|
||||
|
||||
String getSourceTag() {
|
||||
return sourceTag;
|
||||
}
|
||||
|
||||
String getDestTag() {
|
||||
return destTag;
|
||||
}
|
||||
|
||||
boolean isPreserveSourceAttributes() {
|
||||
return preserveSourceAttributes;
|
||||
}
|
||||
|
||||
Map getAttributeTransformations() {
|
||||
return attributeTransformations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Common utilities.
|
||||
* </p>
|
||||
*/
|
||||
final public class Utils {
|
||||
final public static String VAR_START = "${";
|
||||
final public static String VAR_END = "}";
|
||||
|
||||
public static final Map<Character, String> RESERVED_XML_CHARS = new HashMap<Character, String>();
|
||||
|
||||
static {
|
||||
RESERVED_XML_CHARS.put('&', "&");
|
||||
RESERVED_XML_CHARS.put('<', "<");
|
||||
RESERVED_XML_CHARS.put('>', ">");
|
||||
RESERVED_XML_CHARS.put('\"', """);
|
||||
RESERVED_XML_CHARS.put('\'', "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims specified string from left.
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public static String ltrim(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
final int len = s.length();
|
||||
|
||||
while (index < len && Character.isWhitespace(s.charAt(index))) {
|
||||
index++;
|
||||
}
|
||||
|
||||
return (index >= len) ? "" : s.substring(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims specified string from right.
|
||||
*
|
||||
* @param s
|
||||
*/
|
||||
public static String rtrim(final String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int len = s.length();
|
||||
int index = len;
|
||||
|
||||
while (index > 0 && Character.isWhitespace(s.charAt(index - 1))) {
|
||||
index--;
|
||||
}
|
||||
|
||||
return (index <= 0) ? "" : s.substring(0, index);
|
||||
}
|
||||
|
||||
public static String getCharsetFromContentTypeString(final String contentType) {
|
||||
if (contentType != null) {
|
||||
final String pattern = "charset=([a-z\\d\\-]*)";
|
||||
final Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(contentType);
|
||||
if (matcher.find()) {
|
||||
final String charset = matcher.group(1);
|
||||
if (Charset.isSupported(charset)) {
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getCharsetFromContent(final URL url) throws IOException {
|
||||
final InputStream stream = url.openStream();
|
||||
final byte chunk[] = new byte[2048];
|
||||
final int bytesRead = stream.read(chunk);
|
||||
if (bytesRead > 0) {
|
||||
final String startContent = new String(chunk);
|
||||
final String pattern = "\\<meta\\s*http-equiv=[\\\"\\']content-type[\\\"\\']\\s*content\\s*=\\s*[\"']text/html\\s*;\\s*charset=([a-z\\d\\-]*)[\\\"\\'\\>]";
|
||||
final Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(startContent);
|
||||
if (matcher.find()) {
|
||||
final String charset = matcher.group(1);
|
||||
if (Charset.isSupported(charset)) {
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isHexadecimalDigit(final char ch) {
|
||||
return Character.isDigit(ch) || ch == 'A' || ch == 'a' || ch == 'B' || ch == 'b' || ch == 'C' || ch == 'c'
|
||||
|| ch == 'D' || ch == 'd' || ch == 'E' || ch == 'e' || ch == 'F' || ch == 'f';
|
||||
}
|
||||
|
||||
public static boolean isValidXmlChar(final char ch) {
|
||||
return ((ch >= 0x20) && (ch <= 0xD7FF)) || (ch == 0x9) || (ch == 0xA) || (ch == 0xD)
|
||||
|| ((ch >= 0xE000) && (ch <= 0xFFFD)) || ((ch >= 0x10000) && (ch <= 0x10FFFF));
|
||||
}
|
||||
|
||||
public static boolean isReservedXmlChar(final char ch) {
|
||||
return RESERVED_XML_CHARS.containsKey(ch);
|
||||
}
|
||||
|
||||
public static boolean isValidInt(final String s, final int radix) {
|
||||
try {
|
||||
Integer.parseInt(s, radix);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes XML string.
|
||||
*
|
||||
* @param s
|
||||
* String to be escaped
|
||||
* @param props
|
||||
* Cleaner properties gover affect escaping behaviour
|
||||
* @param isDomCreation
|
||||
* Tells if escaped content will be part of the DOM
|
||||
*/
|
||||
public static String escapeXml(final String s, final CleanerProperties props, final boolean isDomCreation) {
|
||||
final boolean advanced = props.isAdvancedXmlEscape();
|
||||
final boolean recognizeUnicodeChars = props.isRecognizeUnicodeChars();
|
||||
final boolean translateSpecialEntities = props.isTranslateSpecialEntities();
|
||||
|
||||
if (s != null) {
|
||||
final int len = s.length();
|
||||
final StringBuilder result = new StringBuilder(len);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
final char ch = s.charAt(i);
|
||||
|
||||
if (ch == '&') {
|
||||
if ((advanced || recognizeUnicodeChars) && (i < len - 2) && (s.charAt(i + 1) == '#')) {
|
||||
final boolean isHex = Character.toLowerCase(s.charAt(i + 2)) == 'x';
|
||||
int charIndex = i + (isHex ? 3 : 2);
|
||||
final int radix = isHex ? 16 : 10;
|
||||
String unicode = "";
|
||||
while (charIndex < len) {
|
||||
final char currCh = s.charAt(charIndex);
|
||||
if (currCh == ';') {
|
||||
break;
|
||||
} else if (isValidInt(unicode + currCh, radix)) {
|
||||
unicode += currCh;
|
||||
charIndex++;
|
||||
} else {
|
||||
charIndex--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidInt(unicode, radix)) {
|
||||
final char unicodeChar = (char) Integer.parseInt(unicode, radix);
|
||||
if (!isValidXmlChar(unicodeChar)) {
|
||||
i = charIndex;
|
||||
} else if (!isReservedXmlChar(unicodeChar)) {
|
||||
result.append(recognizeUnicodeChars ? String.valueOf(unicodeChar) : "&#" + unicode
|
||||
+ ";");
|
||||
i = charIndex;
|
||||
} else {
|
||||
i = charIndex;
|
||||
result.append("&#" + unicode + ";");
|
||||
}
|
||||
} else {
|
||||
result.append("&");
|
||||
}
|
||||
} else {
|
||||
if (translateSpecialEntities) {
|
||||
// get minimal following sequence required to
|
||||
// recognize some special entitiy
|
||||
final String seq = s.substring(i, i
|
||||
+ Math.min(SpecialEntity.getMaxEntityLength() + 2, len - i));
|
||||
final int semiIndex = seq.indexOf(';');
|
||||
if (semiIndex > 0) {
|
||||
final String entityKey = seq.substring(1, semiIndex);
|
||||
final SpecialEntity entity = SpecialEntity.getEntity(entityKey);
|
||||
if (entity != null) {
|
||||
result.append(props.isTransSpecialEntitiesToNCR() ? entity.getDecimalNCR() : entity
|
||||
.getCharacter());
|
||||
i += entityKey.length() + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (advanced) {
|
||||
final String sub = s.substring(i);
|
||||
boolean isReservedSeq = false;
|
||||
for (Map.Entry<Character, String> entry : RESERVED_XML_CHARS.entrySet()) {
|
||||
final String seq = entry.getValue();
|
||||
if (sub.startsWith(seq)) {
|
||||
result.append(isDomCreation ? entry.getKey() : (props.isTransResCharsToNCR() ? "&#"
|
||||
+ (int) entry.getKey() + ";" : seq));
|
||||
i += seq.length() - 1;
|
||||
isReservedSeq = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isReservedSeq) {
|
||||
result.append(isDomCreation ? "&" : (props.isTransResCharsToNCR() ? "&#" + (int) '&'
|
||||
+ ";" : RESERVED_XML_CHARS.get('&')));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append("&");
|
||||
}
|
||||
} else if (isReservedXmlChar(ch)) {
|
||||
result.append(props.isTransResCharsToNCR() ? "&#" + (int) ch + ";" : (isDomCreation ? ch
|
||||
: RESERVED_XML_CHARS.get(ch)));
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether specified object's string representation is empty string
|
||||
* (containing of only whitespaces).
|
||||
*
|
||||
* @param object
|
||||
* Object whose string representation is checked
|
||||
* @return true, if empty string, false otherwise
|
||||
*/
|
||||
public static boolean isWhitespaceString(final Object object) {
|
||||
if (object != null) {
|
||||
final String s = object.toString();
|
||||
return s != null && "".equals(s.trim());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified character can be part of xml identifier (tag name of
|
||||
* attribute name) and is not standard identifier character.
|
||||
*
|
||||
* @param ch
|
||||
* Character to be checked
|
||||
* @return True if it can be part of xml identifier
|
||||
*/
|
||||
public static boolean isIdentifierHelperChar(final char ch) {
|
||||
return ':' == ch || '.' == ch || '-' == ch || '_' == ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chacks whether specified string can be valid tag name or attribute name
|
||||
* in xml.
|
||||
*
|
||||
* @param s
|
||||
* String to be checked
|
||||
* @return True if string is valid xml identifier, false otherwise
|
||||
*/
|
||||
public static boolean isValidXmlIdentifier(final String s) {
|
||||
if (s != null) {
|
||||
final int len = s.length();
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
final char ch = s.charAt(i);
|
||||
if ((i == 0 && !Character.isUnicodeIdentifierStart(ch) && ch != '_')
|
||||
|| (!Character.isUnicodeIdentifierStart(ch) && !Character.isDigit(ch) && !Utils
|
||||
.isIdentifierHelperChar(ch))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param o
|
||||
* @return True if specified string is null of contains only whitespace
|
||||
* characters
|
||||
*/
|
||||
public static boolean isEmptyString(final Object o) {
|
||||
return o == null || "".equals(o.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates string template for specified map of variables. Template string
|
||||
* can contain dynamic parts in the form of ${VARNAME}. Each such part is
|
||||
* replaced with value of the variable if such exists in the map, or with
|
||||
* empty string otherwise.
|
||||
*
|
||||
* @param template
|
||||
* Template string
|
||||
* @param variables
|
||||
* Map of variables (can be null)
|
||||
* @return Evaluated string
|
||||
*/
|
||||
public static String evaluateTemplate(final String template, final Map variables) {
|
||||
if (template == null) {
|
||||
return template;
|
||||
}
|
||||
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
int startIndex = template.indexOf(VAR_START);
|
||||
int endIndex = -1;
|
||||
|
||||
while (startIndex >= 0 && startIndex < template.length()) {
|
||||
result.append(template.substring(endIndex + 1, startIndex));
|
||||
endIndex = template.indexOf(VAR_END, startIndex);
|
||||
|
||||
if (endIndex > startIndex) {
|
||||
final String varName = template.substring(startIndex + VAR_START.length(), endIndex);
|
||||
final Object resultObj = variables != null ? variables.get(varName.toLowerCase()) : "";
|
||||
result.append(resultObj == null ? "" : resultObj.toString());
|
||||
}
|
||||
|
||||
startIndex = template.indexOf(VAR_START, Math.max(endIndex + VAR_END.length(), startIndex + 1));
|
||||
}
|
||||
|
||||
result.append(template.substring(endIndex + 1));
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String[] tokenize(final String s, final String delimiters) {
|
||||
if (s == null) {
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
final StringTokenizer tokenizer = new StringTokenizer(s, delimiters);
|
||||
final String result[] = new String[tokenizer.countTokens()];
|
||||
int index = 0;
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
result[index++] = tokenizer.nextToken();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void updateTagTransformations(final CleanerTransformations transformations, final String key,
|
||||
final String value) {
|
||||
final int index = key.indexOf('.');
|
||||
|
||||
// new tag transformation case (tagname[=destname[,preserveatts]])
|
||||
if (index <= 0) {
|
||||
String destTag = null;
|
||||
boolean preserveSourceAtts = true;
|
||||
if (value != null) {
|
||||
final String[] tokens = tokenize(value, ",;");
|
||||
if (tokens.length > 0) {
|
||||
destTag = tokens[0];
|
||||
}
|
||||
if (tokens.length > 1) {
|
||||
preserveSourceAtts = "true".equalsIgnoreCase(tokens[1]) || "yes".equalsIgnoreCase(tokens[1])
|
||||
|| "1".equals(tokens[1]);
|
||||
}
|
||||
}
|
||||
final TagTransformation newTagTrans = new TagTransformation(key, destTag, preserveSourceAtts);
|
||||
transformations.addTransformation(newTagTrans);
|
||||
} else { // attribute transformation description
|
||||
final String[] parts = tokenize(key, ".");
|
||||
final String tagName = parts[0];
|
||||
final TagTransformation trans = transformations.getTransformation(tagName);
|
||||
if (trans != null) {
|
||||
trans.addAttributeTransformation(parts[1], value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified link is full URL.
|
||||
*
|
||||
* @param link
|
||||
* @return True, if full URl, false otherwise.
|
||||
*/
|
||||
public static boolean isFullUrl(String link) {
|
||||
if (link == null) {
|
||||
return false;
|
||||
}
|
||||
link = link.trim().toLowerCase();
|
||||
return link.startsWith("http://") || link.startsWith("https://") || link.startsWith("file://");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates full URL for specified page URL and link which could be full,
|
||||
* absolute or relative like there can be found in A or IMG tags.
|
||||
*/
|
||||
public static String fullUrl(String pageUrl, final String link) {
|
||||
if (isFullUrl(link)) {
|
||||
return link;
|
||||
} else if (link != null && link.charAt(0) == '?') {
|
||||
final int qindex = pageUrl.indexOf('?');
|
||||
final int len = pageUrl.length();
|
||||
if (qindex < 0) {
|
||||
return pageUrl + link;
|
||||
} else if (qindex == len - 1) {
|
||||
return pageUrl.substring(0, len - 1) + link;
|
||||
} else {
|
||||
return pageUrl + "&" + link.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
final boolean isLinkAbsolute = (link.charAt(0) == '/');
|
||||
|
||||
if (!isFullUrl(pageUrl)) {
|
||||
pageUrl = "http://" + pageUrl;
|
||||
}
|
||||
|
||||
final int slashIndex = isLinkAbsolute ? pageUrl.indexOf('/', 8) : pageUrl.lastIndexOf('/');
|
||||
if (slashIndex <= 8) {
|
||||
pageUrl += "/";
|
||||
} else {
|
||||
pageUrl = pageUrl.substring(0, slashIndex + 1);
|
||||
}
|
||||
|
||||
return isLinkAbsolute ? pageUrl + link.substring(1) : pageUrl + link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return For xml element name or attribute name returns prefix (part
|
||||
* before :) or null if there is no prefix
|
||||
*/
|
||||
public static String getXmlNSPrefix(final String name) {
|
||||
final int colIndex = name.indexOf(':');
|
||||
if (colIndex > 0) {
|
||||
return name.substring(0, colIndex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return For xml element name or attribute name returns name after prefix
|
||||
* (part after :)
|
||||
*/
|
||||
public static String getXmlName(final String name) {
|
||||
final int colIndex = name.indexOf(':');
|
||||
if (colIndex > 0 && colIndex < name.length() - 1) {
|
||||
return name.substring(colIndex + 1);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Utility for searching cleaned document tree with XPath expressions.
|
||||
* </p>
|
||||
* Examples of supported axes: <code>
|
||||
* <ul>
|
||||
* <li>//div//a</li>
|
||||
* <li>//div//a[@id][@class]</li>
|
||||
* <li>/body/*[1]/@type</li>
|
||||
* <li>//div[3]//a[@id][@href='r/n4']</li>
|
||||
* <li>//div[last() >= 4]//./div[position() = last()])[position() > 22]//li[2]//a</li>
|
||||
* <li>//div[2]/@*[2]</li>
|
||||
* <li>data(//div//a[@id][@class])</li>
|
||||
* <li>//p/last()</li>
|
||||
* <li>//body//div[3][@class]//span[12.2<position()]/@id</li>
|
||||
* <li>data(//a['v' < @id])</li>
|
||||
* </ul>
|
||||
* </code>
|
||||
*/
|
||||
public class XPather {
|
||||
|
||||
// array of basic tokens of which XPath expression is made
|
||||
private String tokenArray[];
|
||||
|
||||
/**
|
||||
* Constructor - creates XPather instance with specified XPath expression.
|
||||
*
|
||||
* @param expression
|
||||
*/
|
||||
public XPather(final String expression) {
|
||||
final StringTokenizer tokenizer = new StringTokenizer(expression, "/()[]\"'=<>", true);
|
||||
final int tokenCount = tokenizer.countTokens();
|
||||
tokenArray = new String[tokenCount];
|
||||
|
||||
int index = 0;
|
||||
|
||||
// this is not real XPath compiler, rather simple way to recognize basic
|
||||
// XPaths expressions
|
||||
// and interpret them against some TagNode instance.
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
tokenArray[index++] = tokenizer.nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection evaluateAgainst(final Collection object, int from, final int to, final boolean isRecursive,
|
||||
final int position, final int last, final boolean isFilterContext, final Collection filterSource)
|
||||
throws XPatherException {
|
||||
if (from >= 0 && to < tokenArray.length && from <= to) {
|
||||
if (tokenArray[from].trim().length() == 0) {
|
||||
return evaluateAgainst(object, from + 1, to, isRecursive, position, last, isFilterContext, filterSource);
|
||||
} else if (isToken("(", from)) {
|
||||
final int closingBracket = findClosingIndex(from, to);
|
||||
if (closingBracket > 0) {
|
||||
final Collection value = evaluateAgainst(object, from + 1, closingBracket - 1, false, position,
|
||||
last, isFilterContext, filterSource);
|
||||
return evaluateAgainst(value, closingBracket + 1, to, false, position, last, isFilterContext,
|
||||
filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if (isToken("[", from)) {
|
||||
final int closingBracket = findClosingIndex(from, to);
|
||||
if (closingBracket > 0 && object instanceof Collection) {
|
||||
final Collection value = filterByCondition(object, from + 1, closingBracket - 1);
|
||||
return evaluateAgainst(value, closingBracket + 1, to, false, position, last, isFilterContext,
|
||||
filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if (isToken("\"", from) || isToken("'", from)) {
|
||||
// string constant
|
||||
final int closingQuote = findClosingIndex(from, to);
|
||||
if (closingQuote > from) {
|
||||
final Collection value = singleton(flatten(from + 1, closingQuote - 1));
|
||||
return evaluateAgainst(value, closingQuote + 1, to, false, position, last, isFilterContext,
|
||||
filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if ((isToken("=", from) || isToken("<", from) || isToken(">", from)) && isFilterContext) {
|
||||
// operator inside filter
|
||||
final boolean logicValue;
|
||||
if (isToken("=", from + 1) && (isToken("<", from) || isToken(">", from))) {
|
||||
final Collection secondObject = evaluateAgainst(filterSource, from + 2, to, false, position, last,
|
||||
isFilterContext, filterSource);
|
||||
logicValue = evaluateLogic(object, secondObject, tokenArray[from] + tokenArray[from + 1]);
|
||||
} else {
|
||||
final Collection secondObject = evaluateAgainst(filterSource, from + 1, to, false, position, last,
|
||||
isFilterContext, filterSource);
|
||||
logicValue = evaluateLogic(object, secondObject, tokenArray[from]);
|
||||
}
|
||||
return singleton(Boolean.valueOf(logicValue));
|
||||
} else if (isToken("/", from)) { // children of the node
|
||||
final boolean goRecursive = isToken("/", from + 1);
|
||||
if (goRecursive) {
|
||||
from++;
|
||||
}
|
||||
if (from < to) {
|
||||
int toIndex = findClosingIndex(from, to) - 1;
|
||||
if (toIndex <= from) {
|
||||
toIndex = to;
|
||||
}
|
||||
final Collection value = evaluateAgainst(object, from + 1, toIndex, goRecursive, 1, last,
|
||||
isFilterContext, filterSource);
|
||||
return evaluateAgainst(value, toIndex + 1, to, false, 1, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
} else if (isFunctionCall(from, to)) {
|
||||
final int closingBracketIndex = findClosingIndex(from + 1, to);
|
||||
final Collection funcValue = evaluateFunction(object, from, to, position, last, isFilterContext);
|
||||
return evaluateAgainst(funcValue, closingBracketIndex + 1, to, false, 1, last, isFilterContext,
|
||||
filterSource);
|
||||
} else if (isValidInteger(tokenArray[from])) {
|
||||
final Collection value = singleton(Integer.valueOf(tokenArray[from]));
|
||||
return evaluateAgainst(value, from + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else if (isValidDouble(tokenArray[from])) {
|
||||
final Collection value = singleton(new Double(tokenArray[from]));
|
||||
return evaluateAgainst(value, from + 1, to, false, position, last, isFilterContext, filterSource);
|
||||
} else {
|
||||
return getElementsByName(object, from, to, isRecursive, isFilterContext);
|
||||
}
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
|
||||
throw new XPatherException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main public method for this class - a way to execute XPath expression
|
||||
* against specified TagNode instance.
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
public Object[] evaluateAgainstNode(final TagNode node) throws XPatherException {
|
||||
if (node == null) {
|
||||
throw new XPatherException("Cannot evaluate XPath expression against null value!");
|
||||
}
|
||||
|
||||
final Collection collectionResult = evaluateAgainst(singleton(node), 0, tokenArray.length - 1, false, 1, 0,
|
||||
false, null);
|
||||
final Object[] array = new Object[collectionResult.size()];
|
||||
|
||||
final Iterator iterator = collectionResult.iterator();
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
array[index++] = iterator.next();
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates specified function. Currently, following XPath functions are
|
||||
* supported: last, position, text, count, data
|
||||
*
|
||||
* @param source
|
||||
* @param from
|
||||
* @param to
|
||||
* @param position
|
||||
* @param last
|
||||
* @return Collection as the result of evaluation.
|
||||
*/
|
||||
private Collection evaluateFunction(final Collection source, final int from, final int to, final int position,
|
||||
final int last, final boolean isFilterContext) throws XPatherException {
|
||||
final String name = tokenArray[from].trim();
|
||||
final ArrayList result = new ArrayList();
|
||||
|
||||
final int size = source.size();
|
||||
final Iterator iterator = source.iterator();
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
final Object curr = iterator.next();
|
||||
index++;
|
||||
if ("last".equals(name)) {
|
||||
result.add(Integer.valueOf(isFilterContext ? last : size));
|
||||
} else if ("position".equals(name)) {
|
||||
result.add(Integer.valueOf(isFilterContext ? position : index));
|
||||
} else if ("text".equals(name)) {
|
||||
if (curr instanceof TagNode) {
|
||||
result.add(((TagNode) curr).getText());
|
||||
} else if (curr instanceof String) {
|
||||
result.add(curr.toString());
|
||||
}
|
||||
} else if ("count".equals(name)) {
|
||||
final Collection argumentEvaluated = evaluateAgainst(source, from + 2, to - 1, false, position, 0,
|
||||
isFilterContext, null);
|
||||
result.add(Integer.valueOf(argumentEvaluated.size()));
|
||||
} else if ("data".equals(name)) {
|
||||
final Collection argumentEvaluated = evaluateAgainst(source, from + 2, to - 1, false, position, 0,
|
||||
isFilterContext, null);
|
||||
final Iterator it = argumentEvaluated.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Object elem = it.next();
|
||||
if (elem instanceof TagNode) {
|
||||
result.add(((TagNode) elem).getText());
|
||||
} else if (elem instanceof String) {
|
||||
result.add(elem.toString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new XPatherException("Unknown function " + name + "!");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates logic operation on two collections.
|
||||
*
|
||||
* @param first
|
||||
* @param second
|
||||
* @param logicOperator
|
||||
* @return Result of logic operation
|
||||
*/
|
||||
private boolean evaluateLogic(final Collection first, final Collection second, final String logicOperator) {
|
||||
if (first == null || first.isEmpty() || second == null || second.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final Object elem1 = first.iterator().next();
|
||||
final Object elem2 = second.iterator().next();
|
||||
if (elem1 instanceof Number && elem2 instanceof Number) {
|
||||
final double d1 = ((Number) elem1).doubleValue();
|
||||
final double d2 = ((Number) elem2).doubleValue();
|
||||
if ("=".equals(logicOperator)) {
|
||||
return d1 == d2;
|
||||
} else if ("<".equals(logicOperator)) {
|
||||
return d1 < d2;
|
||||
} else if (">".equals(logicOperator)) {
|
||||
return d1 > d2;
|
||||
} else if ("<=".equals(logicOperator)) {
|
||||
return d1 <= d2;
|
||||
} else if (">=".equals(logicOperator)) {
|
||||
return d1 >= d2;
|
||||
}
|
||||
} else {
|
||||
final String s1 = toText(elem1);
|
||||
final String s2 = toText(elem2);
|
||||
final int result = s1.compareTo(s2);
|
||||
if ("=".equals(logicOperator)) {
|
||||
return result == 0;
|
||||
} else if ("<".equals(logicOperator)) {
|
||||
return result < 0;
|
||||
} else if (">".equals(logicOperator)) {
|
||||
return result > 0;
|
||||
} else if ("<=".equals(logicOperator)) {
|
||||
return result <= 0;
|
||||
} else if (">=".equals(logicOperator)) {
|
||||
return result >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter nodes satisfying the condition
|
||||
*
|
||||
* @param source
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
private final Collection filterByCondition(final Collection source, final int from, final int to)
|
||||
throws XPatherException {
|
||||
final ArrayList result = new ArrayList();
|
||||
final Iterator iterator = source.iterator();
|
||||
int index = 0;
|
||||
final int size = source.size();
|
||||
while (iterator.hasNext()) {
|
||||
final Object curr = iterator.next();
|
||||
index++;
|
||||
|
||||
final ArrayList logicValueList = new ArrayList(evaluateAgainst(singleton(curr), from, to, false, index,
|
||||
size, true, singleton(curr)));
|
||||
if (logicValueList.size() >= 1) {
|
||||
final Object first = logicValueList.get(0);
|
||||
if (first instanceof Boolean) {
|
||||
if (((Boolean) first).booleanValue()) {
|
||||
result.add(curr);
|
||||
}
|
||||
} else if (first instanceof Integer) {
|
||||
if (((Integer) first).intValue() == index) {
|
||||
result.add(curr);
|
||||
}
|
||||
} else {
|
||||
result.add(curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from
|
||||
* @param to
|
||||
* @return matching closing index in the token array for the current token,
|
||||
* or -1 if there is no closing token within expected bounds.
|
||||
*/
|
||||
private int findClosingIndex(final int from, final int to) {
|
||||
if (from < to) {
|
||||
final String currToken = tokenArray[from];
|
||||
|
||||
if ("\"".equals(currToken)) {
|
||||
for (int i = from + 1; i <= to; i++) {
|
||||
if ("\"".equals(tokenArray[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else if ("'".equals(currToken)) {
|
||||
for (int i = from + 1; i <= to; i++) {
|
||||
if ("'".equals(tokenArray[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else if ("(".equals(currToken) || "[".equals(currToken) || "/".equals(currToken)) {
|
||||
boolean isQuoteClosed = true;
|
||||
boolean isAposClosed = true;
|
||||
int brackets = "(".equals(currToken) ? 1 : 0;
|
||||
int angleBrackets = "[".equals(currToken) ? 1 : 0;
|
||||
int slashes = "/".equals(currToken) ? 1 : 0;
|
||||
for (int i = from + 1; i <= to; i++) {
|
||||
if ("\"".equals(tokenArray[i])) {
|
||||
isQuoteClosed = !isQuoteClosed;
|
||||
} else if ("'".equals(tokenArray[i])) {
|
||||
isAposClosed = !isAposClosed;
|
||||
} else if ("(".equals(tokenArray[i]) && isQuoteClosed && isAposClosed) {
|
||||
brackets++;
|
||||
} else if (")".equals(tokenArray[i]) && isQuoteClosed && isAposClosed) {
|
||||
brackets--;
|
||||
} else if ("[".equals(tokenArray[i]) && isQuoteClosed && isAposClosed) {
|
||||
angleBrackets++;
|
||||
} else if ("]".equals(tokenArray[i]) && isQuoteClosed && isAposClosed) {
|
||||
angleBrackets--;
|
||||
} else if ("/".equals(tokenArray[i]) && isQuoteClosed && isAposClosed && brackets == 0
|
||||
&& angleBrackets == 0) {
|
||||
slashes--;
|
||||
}
|
||||
|
||||
if (isQuoteClosed && isAposClosed && brackets == 0 && angleBrackets == 0 && slashes == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String flatten(final int from, final int to) {
|
||||
if (from <= to) {
|
||||
final StringBuffer result = new StringBuffer();
|
||||
for (int i = from; i <= to; i++) {
|
||||
result.append(tokenArray[i]);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given source collection and specified name, returns collection of
|
||||
* subnodes or attribute values.
|
||||
*
|
||||
* @param source
|
||||
* @param from
|
||||
* @param to
|
||||
* @param isRecursive
|
||||
* @return Colection of TagNode instances or collection of String instances.
|
||||
*/
|
||||
private Collection getElementsByName(final Collection source, final int from, final int to,
|
||||
final boolean isRecursive, final boolean isFilterContext) throws XPatherException {
|
||||
String name = tokenArray[from].trim();
|
||||
|
||||
if (isAtt(name)) {
|
||||
name = name.substring(1);
|
||||
final Collection result = new ArrayList();
|
||||
Collection nodes;
|
||||
if (isRecursive) {
|
||||
nodes = new LinkedHashSet();
|
||||
final Iterator iterator = source.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Object next = iterator.next();
|
||||
if (next instanceof TagNode) {
|
||||
final TagNode node = (TagNode) next;
|
||||
nodes.addAll(node.getAllElementsList(true));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodes = source;
|
||||
}
|
||||
|
||||
final Iterator iterator = nodes.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Object next = iterator.next();
|
||||
if (next instanceof TagNode) {
|
||||
final TagNode node = (TagNode) next;
|
||||
if ("*".equals(name)) {
|
||||
result.addAll(evaluateAgainst(node.getAttributes().values(), from + 1, to, false, 1, 1,
|
||||
isFilterContext, null));
|
||||
} else {
|
||||
final String attValue = node.getAttributeByName(name);
|
||||
if (attValue != null) {
|
||||
result.addAll(evaluateAgainst(singleton(attValue), from + 1, to, false, 1, 1,
|
||||
isFilterContext, null));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
final Collection result = new LinkedHashSet();
|
||||
final Iterator iterator = source.iterator();
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
final Object next = iterator.next();
|
||||
if (next instanceof TagNode) {
|
||||
final TagNode node = (TagNode) next;
|
||||
index++;
|
||||
final boolean isSelf = ".".equals(name);
|
||||
final boolean isParent = "..".equals(name);
|
||||
final boolean isAll = "*".equals(name);
|
||||
|
||||
final Collection subnodes;
|
||||
if (isSelf) {
|
||||
subnodes = singleton(node);
|
||||
} else if (isParent) {
|
||||
final TagNode parent = node.getParent();
|
||||
subnodes = parent != null ? singleton(parent) : new ArrayList();
|
||||
} else {
|
||||
subnodes = isAll ? node.getChildTagList() : node.getElementListByName(name, false);
|
||||
}
|
||||
|
||||
final LinkedHashSet nodeSet = new LinkedHashSet(subnodes);
|
||||
final Collection refinedSubnodes = evaluateAgainst(nodeSet, from + 1, to, false, index, nodeSet
|
||||
.size(), isFilterContext, null);
|
||||
|
||||
if (isRecursive) {
|
||||
final List childTags = node.getChildTagList();
|
||||
if (isSelf || isParent || isAll) {
|
||||
result.addAll(refinedSubnodes);
|
||||
}
|
||||
final Iterator childIterator = childTags.iterator();
|
||||
while (childIterator.hasNext()) {
|
||||
final TagNode childTag = (TagNode) childIterator.next();
|
||||
final Collection childrenByName = getElementsByName(singleton(childTag), from, to,
|
||||
isRecursive, isFilterContext);
|
||||
if (!isSelf && !isParent && !isAll && refinedSubnodes.contains(childTag)) {
|
||||
result.add(childTag);
|
||||
}
|
||||
result.addAll(childrenByName);
|
||||
}
|
||||
} else {
|
||||
result.addAll(refinedSubnodes);
|
||||
}
|
||||
} else {
|
||||
throwStandardException();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if token is attribute (starts with @)
|
||||
*
|
||||
* @param token
|
||||
*/
|
||||
private boolean isAtt(final String token) {
|
||||
return token != null && token.length() > 1 && token.charAt(0) == '@';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if tokens in specified range represents valid function call.
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
* @return True if it is valid function call, false otherwise.
|
||||
*/
|
||||
private boolean isFunctionCall(final int from, final int to) {
|
||||
if (!isIdentifier(tokenArray[from]) && !isToken("(", from + 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return findClosingIndex(from + 1, to) > from + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given string is valid identifier.
|
||||
*
|
||||
* @param str
|
||||
*/
|
||||
private boolean isIdentifier(String str) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
str = str.trim();
|
||||
if (str.length() > 0) {
|
||||
if (!Character.isLetter(str.charAt(0))) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 1; i < str.length(); i++) {
|
||||
final char ch = str.charAt(i);
|
||||
if (ch != '_' && ch != '-' && !Character.isLetterOrDigit(ch)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isToken(final String token, final int index) {
|
||||
final int len = tokenArray.length;
|
||||
return index >= 0 && index < len && tokenArray[index].trim().equals(token.trim());
|
||||
}
|
||||
|
||||
private boolean isValidDouble(final String s) {
|
||||
try {
|
||||
Double.parseDouble(s);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidInteger(final String s) {
|
||||
try {
|
||||
Integer.parseInt(s);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates one-element collection for the specified object.
|
||||
*
|
||||
* @param element
|
||||
*/
|
||||
private Collection<Object> singleton(final Object element) {
|
||||
final ArrayList<Object> result = new ArrayList();
|
||||
result.add(element);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void throwStandardException() throws XPatherException {
|
||||
throw new XPatherException();
|
||||
}
|
||||
|
||||
private String toText(final Object o) {
|
||||
if (o == null) {
|
||||
return "";
|
||||
}
|
||||
if (o instanceof TagNode) {
|
||||
return ((TagNode) o).getText().toString();
|
||||
} else {
|
||||
return o.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Exception that could occure during XPather evaluation.
|
||||
* </p>
|
||||
*/
|
||||
public class XPatherException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public XPatherException() {
|
||||
this("Error in evaluating XPath expression!");
|
||||
}
|
||||
|
||||
public XPatherException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public XPatherException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public XPatherException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 Zheng Sun
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
/* Copyright (c) 2006-2007, Vladimir Nikic
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* The name of HtmlCleaner may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
You can contact Vladimir Nikic by sending e-mail to
|
||||
nikic_vladimir@yahoo.com. Please include the word "HtmlCleaner" in the
|
||||
subject line.
|
||||
*/
|
||||
|
||||
package org.htmlcleaner;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Abstract XML serializer - contains common logic for descendants.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class XmlSerializer extends Serializer {
|
||||
|
||||
protected XmlSerializer(final CleanerProperties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToStream() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToStream(final TagNode tagNode, final OutputStream out, final String charset)
|
||||
throws IOException {
|
||||
super.writeToStream(tagNode, out, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToStream() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToStream(final TagNode tagNode, final OutputStream out) throws IOException {
|
||||
super.writeToStream(tagNode, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToFile() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToFile(final TagNode tagNode, final String fileName, final String charset) throws IOException {
|
||||
super.writeToFile(tagNode, fileName, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use writeToFile() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXmlToFile(final TagNode tagNode, final String fileName) throws IOException {
|
||||
super.writeToFile(tagNode, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAsString() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getXmlAsString(final TagNode tagNode, final String charset) throws IOException {
|
||||
return super.getAsString(tagNode, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAsString() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getXmlAsString(final TagNode tagNode) throws IOException {
|
||||
return super.getAsString(tagNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use write() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeXml(final TagNode tagNode, final Writer writer, final String charset) throws IOException {
|
||||
super.write(tagNode, writer, charset);
|
||||
}
|
||||
|
||||
protected String escapeXml(final String xmlContent) {
|
||||
return Utils.escapeXml(xmlContent, props, false);
|
||||
}
|
||||
|
||||
protected boolean dontEscape(final TagNode tagNode) {
|
||||
return props.isUseCdataForScriptAndStyle() && isScriptOrStyle(tagNode);
|
||||
}
|
||||
|
||||
protected boolean isMinimizedTagSyntax(final TagNode tagNode) {
|
||||
final TagInfo tagInfo = props.getTagInfoProvider().getTagInfo(tagNode.getName());
|
||||
return tagNode.getChildren().size() == 0
|
||||
&& (props.isUseEmptyElementTags() || (tagInfo != null && tagInfo.isEmptyTag()));
|
||||
}
|
||||
|
||||
protected void serializeOpenTag(final TagNode tagNode, final Writer writer, final boolean newLine)
|
||||
throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean nsAware = props.isNamespacesAware();
|
||||
|
||||
Set<String> definedNSPrefixes = null;
|
||||
Set<String> additionalNSDeclNeeded = null;
|
||||
|
||||
final String tagPrefix = Utils.getXmlNSPrefix(tagName);
|
||||
if (tagPrefix != null) {
|
||||
if (nsAware) {
|
||||
definedNSPrefixes = new HashSet<String>();
|
||||
tagNode.collectNamespacePrefixesOnPath(definedNSPrefixes);
|
||||
if (!definedNSPrefixes.contains(tagPrefix)) {
|
||||
additionalNSDeclNeeded = new TreeSet<String>();
|
||||
additionalNSDeclNeeded.add(tagPrefix);
|
||||
}
|
||||
} else {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
writer.write("<" + tagName);
|
||||
|
||||
// write attributes
|
||||
for (Map.Entry<String, String> entry : tagNode.getAttributes().entrySet()) {
|
||||
String attName = entry.getKey();
|
||||
final String attPrefix = Utils.getXmlNSPrefix(attName);
|
||||
if (attPrefix != null) {
|
||||
if (nsAware) {
|
||||
// collect used namespace prefixes in attributes in order to
|
||||
// explicitly define
|
||||
// ns declaration if needed; otherwise it would be
|
||||
// ill-formed xml
|
||||
if (definedNSPrefixes == null) {
|
||||
definedNSPrefixes = new HashSet<String>();
|
||||
tagNode.collectNamespacePrefixesOnPath(definedNSPrefixes);
|
||||
}
|
||||
if (!definedNSPrefixes.contains(attPrefix)) {
|
||||
if (additionalNSDeclNeeded == null) {
|
||||
additionalNSDeclNeeded = new TreeSet<String>();
|
||||
}
|
||||
additionalNSDeclNeeded.add(attPrefix);
|
||||
}
|
||||
} else {
|
||||
attName = Utils.getXmlName(attName);
|
||||
}
|
||||
}
|
||||
writer.write(" " + attName + "=\"" + escapeXml(entry.getValue()) + "\"");
|
||||
}
|
||||
|
||||
// write namespace declarations
|
||||
if (nsAware) {
|
||||
final Map<String, String> nsDeclarations = tagNode.getNamespaceDeclarations();
|
||||
if (nsDeclarations != null) {
|
||||
for (Map.Entry<String, String> entry : nsDeclarations.entrySet()) {
|
||||
final String prefix = entry.getKey();
|
||||
String att = "xmlns";
|
||||
if (prefix.length() > 0) {
|
||||
att += ":" + prefix;
|
||||
}
|
||||
writer.write(" " + att + "=\"" + escapeXml(entry.getValue()) + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write additional namespace declarations needed for this tag in order
|
||||
// xml to be well-formed
|
||||
if (additionalNSDeclNeeded != null) {
|
||||
for (String prefix : additionalNSDeclNeeded) {
|
||||
writer.write(" xmlns:" + prefix + "=\"" + prefix + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (isMinimizedTagSyntax(tagNode)) {
|
||||
writer.write(" />");
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
} else if (dontEscape(tagNode)) {
|
||||
writer.write("><![CDATA[");
|
||||
} else {
|
||||
writer.write(">");
|
||||
}
|
||||
}
|
||||
|
||||
protected void serializeEndTag(final TagNode tagNode, final Writer writer, final boolean newLine)
|
||||
throws IOException {
|
||||
String tagName = tagNode.getName();
|
||||
|
||||
if (Utils.isEmptyString(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dontEscape(tagNode)) {
|
||||
writer.write("]]>");
|
||||
}
|
||||
|
||||
if (Utils.getXmlNSPrefix(tagName) != null && !props.isNamespacesAware()) {
|
||||
tagName = Utils.getXmlName(tagName);
|
||||
}
|
||||
writer.write("</" + tagName + ">");
|
||||
|
||||
if (newLine) {
|
||||
writer.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user