First Commit

This commit is contained in:
David Sun
2011-09-23 07:29:21 +00:00
commit fc51385483
73 changed files with 18283 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
HtmlCleaner is a project originally developed by Vladimir Nikic (http://htmlcleaner.sourceforge.net/).
This version is modified by Zheng Sun.
+227
View File
@@ -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("]]>", "]]&gt;") : 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("]]>", "]]&gt;") : 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("]]>", "]]&gt;") );
}
} 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("]]>", "]]&gt;") : 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("]]>", "]]&gt;") : 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>&lt;a href="#"&gt;&lt;div&gt;....</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>&lt;i&gt;&lt;b&gt;at&lt;/i&gt; first&lt;/b&gt; text </code>
* tag B must be closed before closing I, but it must be copied again after resulting
* finally in sequence: <code>&lt;i&gt;&lt;b&gt;at&lt;/b&gt;&lt;/i&gt;&lt;b&gt; first&lt;/b&gt; 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('&', "&amp;");
RESERVED_XML_CHARS.put('<', "&lt;");
RESERVED_XML_CHARS.put('>', "&gt;");
RESERVED_XML_CHARS.put('\"', "&quot;");
RESERVED_XML_CHARS.put('\'', "&apos;");
}
/**
* 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("&amp;");
}
} 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("&amp;");
}
} 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");
}
}
}
+67
View File
@@ -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("]]>", "]]&gt;") : 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);
}
}
}
+277
View File
@@ -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());
}
}
+86
View File
@@ -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("]]>", "]]&gt;") : 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);
}
}
}
+88
View File
@@ -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();
}
}
+522
View File
@@ -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());
}
}
}
+146
View File
@@ -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();
}
}
+157
View File
@@ -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);
}
}
}
}
}
+80
View File
@@ -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);
}
}
}
}
}
}
+932
View File
@@ -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);
}
}
+23
View File
@@ -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 {
}
+236
View File
@@ -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");
}
}
}
+836
View File
@@ -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();
}
}
}
+66
View File
@@ -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);
}
}
}
+338
View File
@@ -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("]]>", "]]&gt;") : escapeXml(content));
} else if (item instanceof BaseToken) {
((BaseToken) item).serialize(this, writer);
}
}
serializeEndTag(tagNode, writer, false);
}
}
}
+398
View File
@@ -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;
}
}
+406
View File
@@ -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>&lt;a href="#"&gt;&lt;div&gt;....</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>&lt;i&gt;&lt;b&gt;at&lt;/i&gt; first&lt;/b&gt; text </code>
* tag B must be closed before closing I, but it must be copied again after
* resulting finally in sequence:
* <code>&lt;i&gt;&lt;b&gt;at&lt;/b&gt;&lt;/i&gt;&lt;b&gt; first&lt;/b&gt; 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;
}
}
+765
View File
@@ -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;
}
}
+36
View File
@@ -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);
}
+81
View File
@@ -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);
}
+164
View File
@@ -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;
}
}
+519
View File
@@ -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('&', "&amp;");
RESERVED_XML_CHARS.put('<', "&lt;");
RESERVED_XML_CHARS.put('>', "&gt;");
RESERVED_XML_CHARS.put('\"', "&quot;");
RESERVED_XML_CHARS.put('\'', "&apos;");
}
/**
* 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("&amp;");
}
} 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("&amp;");
}
} 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;
}
}
+626
View File
@@ -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();
}
}
}
+80
View File
@@ -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);
}
}
+254
View File
@@ -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");
}
}
}