+ * Base token interface. Tokens are individual entities recognized by HTML parser.
+ *
+ */
+public interface BaseToken {
+
+ public void serialize(Serializer serializer, Writer writer) throws IOException;
+
+}
diff --git a/src/org/htmlcleaner/.svn/text-base/BrowserCompactXmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/BrowserCompactXmlSerializer.java.svn-base
new file mode 100644
index 0000000..91fa360
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/BrowserCompactXmlSerializer.java.svn-base
@@ -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;
+
+/**
+ *
+ * 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.
+ *
+ */
+public class BrowserCompactXmlSerializer extends XmlSerializer {
+
+ public BrowserCompactXmlSerializer(CleanerProperties props) {
+ super(props);
+ }
+
+ protected void serialize(TagNode tagNode, Writer writer) throws IOException {
+ serializeOpenTag(tagNode, writer, false);
+
+ List tagChildren = tagNode.getChildren();
+ if ( !isMinimizedTagSyntax(tagNode) ) {
+ ListIterator childrenIt = tagChildren.listIterator();
+ while ( childrenIt.hasNext() ) {
+ Object item = childrenIt.next();
+ if (item instanceof ContentNode) {
+ String content = item.toString();
+ boolean startsWithSpace = content.length() > 0 && Character.isWhitespace( content.charAt(0) );
+ boolean endsWithSpace = content.length() > 1 && Character.isWhitespace( content.charAt(content.length() - 1) );
+ content = dontEscape(tagNode) ? content.trim().replaceAll("]]>", "]]>") : escapeXml(content.trim());
+
+ if (startsWithSpace) {
+ writer.write(' ');
+ }
+
+ if (content.length() != 0) {
+ writer.write(content);
+ if (endsWithSpace) {
+ writer.write(' ');
+ }
+ }
+
+ if (childrenIt.hasNext()) {
+ if ( !Utils.isWhitespaceString(childrenIt.next()) ) {
+ writer.write("\n");
+ }
+ childrenIt.previous();
+ }
+ } else if (item instanceof CommentNode) {
+ String content = ((CommentNode) item).getCommentedContent().trim();
+ writer.write(content);
+ } else if (item instanceof BaseToken) {
+ ((BaseToken)item).serialize(this, writer);
+ }
+ }
+
+ serializeEndTag(tagNode, writer, false);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/CleanerProperties.java.svn-base b/src/org/htmlcleaner/.svn/text-base/CleanerProperties.java.svn-base
new file mode 100644
index 0000000..2f54823
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/CleanerProperties.java.svn-base
@@ -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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/CleanerTransformations.java.svn-base b/src/org/htmlcleaner/.svn/text-base/CleanerTransformations.java.svn-base
new file mode 100644
index 0000000..bab554c
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/CleanerTransformations.java.svn-base
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/CommandLine.java.svn-base b/src/org/htmlcleaner/.svn/text-base/CommandLine.java.svn-base
new file mode 100644
index 0000000..b4e790f
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/CommandLine.java.svn-base
@@ -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;
+
+/**
+ *
Command line usage class.
+ */
+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 = [incharset = ] " +
+ "[dest = ] [outcharset = ] [taginfofile=] [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= [=]");
+ System.err.println(" prunetags= []");
+ System.err.println(" booleanatts=self* | empty | true");
+ System.err.println(" nodebyxpath=");
+ System.err.println(" omitenvelope=true | false*");
+ System.err.println(" t:[=[,]]");
+ System.err.println(" t:.[=]");
+ 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." );
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/CommentNode.java.svn-base b/src/org/htmlcleaner/.svn/text-base/CommentNode.java.svn-base
new file mode 100644
index 0000000..0d94fd9
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/CommentNode.java.svn-base
@@ -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;
+
+/**
+ *
HTML comment token.
+ */
+public class CommentNode implements BaseToken, HtmlNode {
+
+ private StringBuilder content;
+
+ public CommentNode(String content) {
+ this.content = new StringBuilder(content);
+ }
+
+ public String getCommentedContent() {
+ return "";
+ }
+
+ public StringBuilder getContent() {
+ return content;
+ }
+
+ public String toString() {
+ return getCommentedContent();
+ }
+
+ public void serialize(Serializer serializer, Writer writer) throws IOException {
+ writer.write( getCommentedContent() );
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/CompactHtmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/CompactHtmlSerializer.java.svn-base
new file mode 100644
index 0000000..f9fdbf7
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/CompactHtmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Compact HTML serializer - creates resulting HTML by stripping whitespaces wherever possible.
+ */
+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--;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/CompactXmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/CompactXmlSerializer.java.svn-base
new file mode 100644
index 0000000..5dcfce4
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/CompactXmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Compact XML serializer - creates resulting XML by stripping whitespaces.
+ */
+public class CompactXmlSerializer extends XmlSerializer {
+
+ public CompactXmlSerializer(CleanerProperties props) {
+ super(props);
+ }
+
+ protected void serialize(TagNode tagNode, Writer writer) throws IOException {
+ serializeOpenTag(tagNode, writer, false);
+
+ List tagChildren = tagNode.getChildren();
+ if ( !isMinimizedTagSyntax(tagNode) ) {
+ ListIterator childrenIt = tagChildren.listIterator();
+ while ( childrenIt.hasNext() ) {
+ Object item = childrenIt.next();
+ if (item instanceof ContentNode) {
+ String content = item.toString().trim();
+ writer.write( dontEscape(tagNode) ? content.replaceAll("]]>", "]]>") : escapeXml(content) );
+
+ if (childrenIt.hasNext()) {
+ if ( !Utils.isWhitespaceString(childrenIt.next()) ) {
+ writer.write("\n");
+ }
+ childrenIt.previous();
+ }
+ } else if (item instanceof CommentNode) {
+ String content = ((CommentNode) item).getCommentedContent().trim();
+ writer.write(content);
+ } else if (item instanceof BaseToken) {
+ ((BaseToken)item).serialize(this, writer);
+ }
+ }
+
+ serializeEndTag(tagNode, writer, false);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/ConfigFileTagProvider.java.svn-base b/src/org/htmlcleaner/.svn/text-base/ConfigFileTagProvider.java.svn-base
new file mode 100644
index 0000000..4f55ecf
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/ConfigFileTagProvider.java.svn-base
@@ -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;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/ContentNode.java.svn-base b/src/org/htmlcleaner/.svn/text-base/ContentNode.java.svn-base
new file mode 100644
index 0000000..c657d83
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/ContentNode.java.svn-base
@@ -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;
+
+/**
+ *
HTML text token.
+ */
+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() );
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/DefaultTagProvider.java.svn-base b/src/org/htmlcleaner/.svn/text-base/DefaultTagProvider.java.svn-base
new file mode 100644
index 0000000..290d1f8
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/DefaultTagProvider.java.svn-base
@@ -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 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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/DoctypeToken.java.svn-base b/src/org/htmlcleaner/.svn/text-base/DoctypeToken.java.svn-base
new file mode 100644
index 0000000..807bd39
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/DoctypeToken.java.svn-base
@@ -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;
+
+/**
+ *
+ */
+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 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 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);
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/EndTagToken.java.svn-base b/src/org/htmlcleaner/.svn/text-base/EndTagToken.java.svn-base
new file mode 100644
index 0000000..e3818bc
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/EndTagToken.java.svn-base
@@ -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;
+
+
+/**
+ *
HTML tag end token.
+ */
+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
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/HtmlCleaner.java.svn-base b/src/org/htmlcleaner/.svn/text-base/HtmlCleaner.java.svn-base
new file mode 100644
index 0000000..bbccdaa
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/HtmlCleaner.java.svn-base
@@ -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.
+ *
+ *
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.
+ *
Typical usage is the following:
+ *
+ *
+ * // 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);
+ *
+ */
+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 list = new ArrayList();
+ private TagPos last = null;
+ private Set set = new HashSet();
+
+ 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 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 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 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 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 pruneTagSet = new HashSet();
+ private Set pruneNodeSet = new HashSet();
+ }
+
+ 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 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 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 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 nodeList, ListIterator 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 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 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() );
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/HtmlCleanerException.java.svn-base b/src/org/htmlcleaner/.svn/text-base/HtmlCleanerException.java.svn-base
new file mode 100644
index 0000000..08169ec
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/HtmlCleanerException.java.svn-base
@@ -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;
+
+
+/**
+ *
General HtmlCleaner runtime exception.
+ */
+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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/HtmlCleanerForAnt.java.svn-base b/src/org/htmlcleaner/.svn/text-base/HtmlCleanerForAnt.java.svn-base
new file mode 100644
index 0000000..8b63719
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/HtmlCleanerForAnt.java.svn-base
@@ -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.*;
+
+/**
+ *
Support for ANT.
+ */
+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 [=]|[=...
+ // (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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/HtmlNode.java.svn-base b/src/org/htmlcleaner/.svn/text-base/HtmlNode.java.svn-base
new file mode 100644
index 0000000..1e8afbb
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/HtmlNode.java.svn-base
@@ -0,0 +1,7 @@
+package org.htmlcleaner;
+
+/**
+ * Marker interface denoting nodes of the document tree
+ */
+public interface HtmlNode {
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/HtmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/HtmlSerializer.java.svn-base
new file mode 100644
index 0000000..b7e1a88
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/HtmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Abstract HTML serializer - contains common logic for descendants.
+ */
+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 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 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 nsDeclarations = tagNode.getNamespaceDeclarations();
+ if (nsDeclarations != null) {
+ for (Map.Entry 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");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/HtmlTokenizer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/HtmlTokenizer.java.svn-base
new file mode 100644
index 0000000..86b73be
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/HtmlTokenizer.java.svn-base
@@ -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.
+ *
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.
+ */
+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 _tokenList = new ArrayList();
+
+ 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 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 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("')) ) {
+ tagEnd();
+ } else if ( isScriptEmpty && 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/ITagInfoProvider.java.svn-base b/src/org/htmlcleaner/.svn/text-base/ITagInfoProvider.java.svn-base
new file mode 100644
index 0000000..5b8bc44
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/ITagInfoProvider.java.svn-base
@@ -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;
+
+/**
+ *
+ * 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.
+ * In most cases implementation will be or contain a kind of Map.
+ *
JDom serializer - creates xml JDom instance out of the TagNode.
+ */
+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 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 nsDeclarations = node.getNamespaceDeclarations();
+ if (nsDeclarations != null) {
+ for (Map.Entry 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 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);
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/PrettyHtmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/PrettyHtmlSerializer.java.svn-base
new file mode 100644
index 0000000..d609cc8
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/PrettyHtmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Pretty HTML serializer - creates resulting HTML with indenting lines.
+ */
+public class PrettyHtmlSerializer extends HtmlSerializer {
+
+ private static final String DEFAULT_INDENTATION_STRING = "\t";
+
+ private String indentString = DEFAULT_INDENTATION_STRING;
+ private List indents = new ArrayList();
+
+ 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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/PrettyXmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/PrettyXmlSerializer.java.svn-base
new file mode 100644
index 0000000..b82507f
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/PrettyXmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Pretty XML serializer - creates resulting XML with indenting lines.
+ */
+public class PrettyXmlSerializer extends XmlSerializer {
+
+ private static final String DEFAULT_INDENTATION_STRING = "\t";
+
+ private String indentString = DEFAULT_INDENTATION_STRING;
+ private List indents = new ArrayList();
+
+ public PrettyXmlSerializer(CleanerProperties props) {
+ this(props, DEFAULT_INDENTATION_STRING);
+ }
+
+ public PrettyXmlSerializer(CleanerProperties props, String indentString) {
+ super(props);
+ this.indentString = indentString;
+ }
+
+ protected void serialize(TagNode tagNode, Writer writer) throws IOException {
+ serializePrettyXml(tagNode, writer, 0);
+ }
+
+ /**
+ * @param level
+ * @return Appropriate indentation for the specified depth.
+ */
+ private synchronized String getIndent(int level) {
+ int size = indents.size();
+ if (size <= level) {
+ String prevIndent = size == 0 ? null : indents.get(size - 1);
+ for (int i = size; i <= level; i++) {
+ String currIndent = prevIndent == null ? "" : prevIndent + indentString;
+ indents.add(currIndent);
+ prevIndent = currIndent;
+ }
+ }
+
+ return indents.get(level);
+ }
+
+ private String getIndentedText(String content, int level) {
+ String indent = getIndent(level);
+ StringBuilder result = new StringBuilder( content.length() );
+ StringTokenizer tokenizer = new StringTokenizer(content, "\n\r");
+
+ while (tokenizer.hasMoreTokens()) {
+ String line = tokenizer.nextToken().trim();
+ if (!"".equals(line)) {
+ result.append(indent).append(line).append("\n");
+ }
+ }
+
+ return result.toString();
+ }
+
+ private String getSingleLineOfChildren(List children) {
+ StringBuilder result = new StringBuilder();
+ Iterator childrenIt = children.iterator();
+ boolean isFirst = true;
+
+ while (childrenIt.hasNext()) {
+ Object child = childrenIt.next();
+
+ if ( !(child instanceof ContentNode) ) {
+ return null;
+ } else {
+ String content = child.toString();
+
+ // if first item trims it from left
+ if (isFirst) {
+ content = Utils.ltrim(content);
+ }
+
+ // if last item trims it from right
+ if (!childrenIt.hasNext()) {
+ content = Utils.rtrim(content);
+ }
+
+ if ( content.indexOf("\n") >= 0 || content.indexOf("\r") >= 0 ) {
+ return null;
+ }
+ result.append(content);
+ }
+
+ isFirst = false;
+ }
+
+ return result.toString();
+ }
+
+ protected void serializePrettyXml(TagNode tagNode, Writer writer, int level) throws IOException {
+ List tagChildren = tagNode.getChildren();
+ boolean isHeadlessNode = Utils.isEmptyString(tagNode.getName());
+ String indent = isHeadlessNode ? "" : getIndent(level);
+
+ writer.write(indent);
+ serializeOpenTag(tagNode, writer, true);
+
+ if ( !isMinimizedTagSyntax(tagNode) ) {
+ String singleLine = getSingleLineOfChildren(tagChildren);
+ boolean dontEscape = dontEscape(tagNode);
+ if (singleLine != null) {
+ if ( !dontEscape(tagNode) ) {
+ writer.write( escapeXml(singleLine) );
+ } else {
+ writer.write( singleLine.replaceAll("]]>", "]]>") );
+ }
+ } else {
+ if (!isHeadlessNode) {
+ writer.write("\n");
+ }
+ for (Object child: tagChildren) {
+ if (child instanceof TagNode) {
+ serializePrettyXml( (TagNode)child, writer, isHeadlessNode ? level : level + 1 );
+ } else if (child instanceof ContentNode) {
+ String content = dontEscape ? child.toString().replaceAll("]]>", "]]>") : escapeXml(child.toString());
+ writer.write( getIndentedText(content, isHeadlessNode ? level : level + 1) );
+ } else if (child instanceof CommentNode) {
+ CommentNode commentNode = (CommentNode) child;
+ String content = commentNode.getCommentedContent();
+ writer.write( getIndentedText(content, isHeadlessNode ? level : level + 1) );
+ }
+ }
+ }
+
+ if (singleLine == null) {
+ writer.write(indent);
+ }
+
+ serializeEndTag(tagNode, writer, true);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/Serializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/Serializer.java.svn-base
new file mode 100644
index 0000000..e019d57
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/Serializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Basic abstract serializer - contains common logic for descendants (methods writeXXX().
+ */
+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 nsDecls = getNamespaceDeclarations();
+ if (nsDecls != null) {
+ Map 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 = "";
+ 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;
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/SimpleHtmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/SimpleHtmlSerializer.java.svn-base
new file mode 100644
index 0000000..f5f0ed1
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/SimpleHtmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Simple HTML serializer - creates resulting HTML without indenting and/or compacting.
+ */
+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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/SimpleXmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/SimpleXmlSerializer.java.svn-base
new file mode 100644
index 0000000..0231b94
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/SimpleXmlSerializer.java.svn-base
@@ -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;
+
+/**
+ *
Simple XML serializer - creates resulting XML without indenting lines.
+ */
+public class SimpleXmlSerializer extends XmlSerializer {
+
+ public SimpleXmlSerializer(CleanerProperties props) {
+ super(props);
+ }
+
+ protected void serialize(TagNode tagNode, Writer writer) throws IOException {
+ serializeOpenTag(tagNode, writer, false);
+
+ if ( !isMinimizedTagSyntax(tagNode) ) {
+ for (Object item: tagNode.getChildren()) {
+ if ( item instanceof ContentNode) {
+ String content = item.toString();
+ writer.write( dontEscape(tagNode) ? content.replaceAll("]]>", "]]>") : escapeXml(content) );
+ } else if (item instanceof BaseToken) {
+ ((BaseToken)item).serialize(this, writer);
+ }
+ }
+
+ serializeEndTag(tagNode, writer, false);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/SpecialEntity.java.svn-base b/src/org/htmlcleaner/.svn/text-base/SpecialEntity.java.svn-base
new file mode 100644
index 0000000..4b6c447
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/SpecialEntity.java.svn-base
@@ -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;
+
+/**
+ *
This class contains map with special entities used in HTML and their unicodes.
+ */
+public class SpecialEntity {
+
+ private static Map entities = new HashMap();
+
+ 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 "" + Integer.toHexString(intCode) + ";";
+ }
+
+ /**
+ * @return Escaped value of the entity
+ */
+ public String getEscapedValue() {
+ return "&" + key + ";";
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/TagInfo.java.svn-base b/src/org/htmlcleaner/.svn/text-base/TagInfo.java.svn-base
new file mode 100644
index 0000000..03af0a9
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/TagInfo.java.svn-base
@@ -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.*;
+
+/**
+ *
+ * Class contains information about single HTML tag.
+ * 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:
+ *
+ *
+ * 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.
+ *
+ *
+ * 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.
+ *
+ *
+ * forbidden tags - it is not allowed to occure inside - for example
+ * FORM cannot be inside other FORM and it will be ignored during cleanup.
+ *
+ *
+ * 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.
+ *
+ *
+ * higher level tags - for example for TR higher tags are THEAD, TBODY, TFOOT.
+ *
+ *
+ * tags that must be closed and copied - for example, in
+ * <a href="#"><div>.... tag A must be closed before DIV but
+ * copied again inside DIV.
+ *
+ *
+ * tags that must be closed before closing this tag and copied again after -
+ * for example, in <i><b>at</i> first</b> text
+ * tag B must be closed before closing I, but it must be copied again after resulting
+ * finally in sequence: <i><b>at</b></i><b> first</b> text .
+ *
+ *
+ *
+ *
+ *
+ * Tag TR for instance (table row) may define the following dependancies:
+ *
+ *
fatal tag is table
+ *
required enclosing tag is tbody
+ *
allowed children tags are td,th
+ *
higher level tags are thead,tfoot
+ *
tags that muste be closed before are tr,td,th,caption,colgroup
+ *
+ * meaning the following:
+ *
+ *
tr must be in context of table, otherwise it will be ignored,
+ *
tr may can be directly inside tbody, tfoot and thead,
+ * otherwise tbody will be implicitely created in front of it.
+ *
tr can contain td and th, all other tags and content will be pushed out of current
+ * limiting context, in the case of html tables, in front of enclosing table tag.
+ *
if previous open tag is one of tr, caption or colgroup, it will be implicitely closed.
+ *
+ *
+ */
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/TagNode.java.svn-base b/src/org/htmlcleaner/.svn/text-base/TagNode.java.svn-base
new file mode 100644
index 0000000..24b79ff
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/TagNode.java.svn-base
@@ -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.*;
+
+/**
+ *
+ * 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).
+ *
+ */
+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 attributes = new LinkedHashMap();
+ private List children = new ArrayList();
+ private DoctypeToken docType = null;
+ private Map nsDeclarations = null;
+ private List 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 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();
+ }
+ nsDeclarations.put(nsPrefix, nsURI);
+ }
+
+ /**
+ * @return Map of namespace declarations for this node
+ */
+ public Map 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.
+ *
+ * This is not fully supported XPath parser and evaluator.
+ * Examples below show supported elements:
+ *
+ *
+ *
+ *
+ * @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();
+ }
+
+ itemsToMove.add(item);
+ }
+
+ List getItemsToMove() {
+ return itemsToMove;
+ }
+
+ void setItemsToMove(List 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 newAttributes = isPreserveSourceAtts ? new LinkedHashMap(attributes) : new LinkedHashMap();
+ 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 prefixes) {
+ Map 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 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/TagNodeVisitor.java.svn-base b/src/org/htmlcleaner/.svn/text-base/TagNodeVisitor.java.svn-base
new file mode 100644
index 0000000..1326514
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/TagNodeVisitor.java.svn-base
@@ -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);
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/TagToken.java.svn-base b/src/org/htmlcleaner/.svn/text-base/TagToken.java.svn-base
new file mode 100644
index 0000000..d5c7832
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/TagToken.java.svn-base
@@ -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;
+
+
+/**
+ *
HTML tag token - descendants are start (TagNode) and end token (EndTagToken).
+ */
+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);
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/TagTransformation.java.svn-base b/src/org/htmlcleaner/.svn/text-base/TagTransformation.java.svn-base
new file mode 100644
index 0000000..3158481
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/TagTransformation.java.svn-base
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/Utils.java.svn-base b/src/org/htmlcleaner/.svn/text-base/Utils.java.svn-base
new file mode 100644
index 0000000..4652a4b
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/Utils.java.svn-base
@@ -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;
+
+/**
+ *
Common utilities.
+ */
+public class Utils {
+
+ public static String VAR_START = "${";
+ public static String VAR_END = "}";
+
+ public static final Map RESERVED_XML_CHARS = new HashMap();
+
+ static {
+ RESERVED_XML_CHARS.put('&', "&");
+ RESERVED_XML_CHARS.put('<', "<");
+ RESERVED_XML_CHARS.put('>', ">");
+ RESERVED_XML_CHARS.put('\"', """);
+ RESERVED_XML_CHARS.put('\'', "'");
+ }
+
+ /**
+ * Trims specified string from left.
+ * @param s
+ */
+ public static String ltrim(String s) {
+ if (s == null) {
+ return null;
+ }
+
+ int index = 0;
+ int len = s.length();
+
+ while ( index < len && Character.isWhitespace(s.charAt(index)) ) {
+ index++;
+ }
+
+ return (index >= len) ? "" : s.substring(index);
+ }
+
+ /**
+ * Trims specified string from right.
+ * @param s
+ */
+ public static String rtrim(String s) {
+ if (s == null) {
+ return null;
+ }
+
+ int len = s.length();
+ int index = len;
+
+ while ( index > 0 && Character.isWhitespace(s.charAt(index-1)) ) {
+ index--;
+ }
+
+ return (index <= 0) ? "" : s.substring(0, index);
+ }
+
+ public static String getCharsetFromContentTypeString(String contentType) {
+ if (contentType != null) {
+ String pattern = "charset=([a-z\\d\\-]*)";
+ Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(contentType);
+ if (matcher.find()) {
+ String charset = matcher.group(1);
+ if (Charset.isSupported(charset)) {
+ return charset;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public static String getCharsetFromContent(URL url) throws IOException {
+ InputStream stream = url.openStream();
+ byte chunk[] = new byte[2048];
+ int bytesRead = stream.read(chunk);
+ if (bytesRead > 0) {
+ String startContent = new String(chunk);
+ String pattern = "\\]";
+ Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(startContent);
+ if (matcher.find()) {
+ String charset = matcher.group(1);
+ if (Charset.isSupported(charset)) {
+ return charset;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public static boolean isHexadecimalDigit(char ch) {
+ return Character.isDigit(ch) ||
+ ch == 'A' || ch == 'a' || ch == 'B' || ch == 'b' || ch == 'C' || ch == 'c' ||
+ ch == 'D' || ch == 'd' || ch == 'E' || ch == 'e' || ch == 'F' || ch == 'f';
+ }
+
+ public static boolean isValidXmlChar(char ch) {
+ return ((ch >= 0x20) && (ch <= 0xD7FF)) ||
+ (ch == 0x9) ||
+ (ch == 0xA) ||
+ (ch == 0xD) ||
+ ((ch >= 0xE000) && (ch <= 0xFFFD)) ||
+ ((ch >= 0x10000) && (ch <= 0x10FFFF));
+ }
+
+ public static boolean isReservedXmlChar(char ch) {
+ return RESERVED_XML_CHARS.containsKey(ch);
+ }
+
+ public static boolean isValidInt(String s, int radix) {
+ try {
+ Integer.parseInt(s, radix);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Escapes XML string.
+ * @param s String to be escaped
+ * @param props Cleaner properties gover affect escaping behaviour
+ * @param isDomCreation Tells if escaped content will be part of the DOM
+ */
+ public static String escapeXml(String s, CleanerProperties props, boolean isDomCreation) {
+ boolean advanced = props.isAdvancedXmlEscape();
+ boolean recognizeUnicodeChars = props.isRecognizeUnicodeChars();
+ boolean translateSpecialEntities = props.isTranslateSpecialEntities();
+
+ if (s != null) {
+ int len = s.length();
+ StringBuilder result = new StringBuilder(len);
+
+ for (int i = 0; i < len; i++) {
+ char ch = s.charAt(i);
+
+ if (ch == '&') {
+ if ( (advanced || recognizeUnicodeChars) && (i < len-2) && (s.charAt(i+1) == '#') ) {
+ boolean isHex = Character.toLowerCase(s.charAt(i+2)) == 'x';
+ int charIndex = i + (isHex ? 3 : 2);
+ int radix = isHex ? 16 : 10;
+ String unicode = "";
+ while (charIndex < len) {
+ char currCh = s.charAt(charIndex);
+ if (currCh == ';') {
+ break;
+ } else if (isValidInt(unicode + currCh, radix)) {
+ unicode += currCh;
+ charIndex++;
+ } else {
+ charIndex--;
+ break;
+ }
+ }
+
+ if (isValidInt(unicode, radix)) {
+ char unicodeChar = (char)Integer.parseInt(unicode, radix);
+ if ( !isValidXmlChar(unicodeChar) ) {
+ i = charIndex;
+ } else if ( !isReservedXmlChar(unicodeChar) ) {
+ result.append( recognizeUnicodeChars ? String.valueOf(unicodeChar) : "" + unicode + ";" );
+ i = charIndex;
+ } else {
+ i = charIndex;
+ result.append("" + unicode + ";");
+ }
+ } else {
+ result.append("&");
+ }
+ } else {
+ if (translateSpecialEntities) {
+ // get minimal following sequence required to recognize some special entitiy
+ String seq = s.substring(i, i + Math.min(SpecialEntity.getMaxEntityLength() + 2, len - i));
+ int semiIndex = seq.indexOf(';');
+ if (semiIndex > 0) {
+ String entityKey = seq.substring(1, semiIndex);
+ SpecialEntity entity = SpecialEntity.getEntity(entityKey);
+ if (entity != null) {
+ result.append(props.isTransSpecialEntitiesToNCR() ? entity.getDecimalNCR() : entity.getCharacter());
+ i += entityKey.length() + 1;
+ continue;
+ }
+ }
+ }
+
+ if (advanced) {
+ String sub = s.substring(i);
+ boolean isReservedSeq = false;
+ for (Map.Entry entry: RESERVED_XML_CHARS.entrySet()) {
+ String seq = entry.getValue();
+ if ( sub.startsWith(seq) ) {
+ result.append( isDomCreation ? entry.getKey() : (props.transResCharsToNCR ? "" + (int)entry.getKey() + ";" : seq) );
+ i += seq.length() - 1;
+ isReservedSeq = true;
+ break;
+ }
+ }
+ if (!isReservedSeq) {
+ result.append( isDomCreation ? "&" : (props.transResCharsToNCR ? "" + (int)'&' + ";" : RESERVED_XML_CHARS.get('&')) );
+ }
+ continue;
+ }
+
+ result.append("&");
+ }
+ } else if (isReservedXmlChar(ch)) {
+ result.append( props.transResCharsToNCR ? "" + (int)ch + ";" : (isDomCreation ? ch : RESERVED_XML_CHARS.get(ch)) );
+ } else {
+ result.append(ch);
+ }
+ }
+
+ return result.toString();
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks whether specified object's string representation is empty string (containing of only whitespaces).
+ * @param object Object whose string representation is checked
+ * @return true, if empty string, false otherwise
+ */
+ public static boolean isWhitespaceString(Object object) {
+ if (object != null) {
+ String s = object.toString();
+ return s != null && "".equals(s.trim());
+ }
+ return false;
+ }
+
+ /**
+ * Checks if specified character can be part of xml identifier (tag name of attribute name)
+ * and is not standard identifier character.
+ * @param ch Character to be checked
+ * @return True if it can be part of xml identifier
+ */
+ public static boolean isIdentifierHelperChar(char ch) {
+ return ':' == ch || '.' == ch || '-' == ch || '_' == ch;
+ }
+
+ /**
+ * Chacks whether specified string can be valid tag name or attribute name in xml.
+ * @param s String to be checked
+ * @return True if string is valid xml identifier, false otherwise
+ */
+ public static boolean isValidXmlIdentifier(String s) {
+ if (s != null) {
+ int len = s.length();
+ if (len == 0) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ char ch = s.charAt(i);
+ if ( (i == 0 && !Character.isUnicodeIdentifierStart(ch) && ch != '_') ||
+ (!Character.isUnicodeIdentifierStart(ch) && !Character.isDigit(ch) && !Utils.isIdentifierHelperChar(ch)) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param o
+ * @return True if specified string is null of contains only whitespace characters
+ */
+ public static boolean isEmptyString(Object o) {
+ return o == null || "".equals(o.toString().trim());
+ }
+
+ /**
+ * Evaluates string template for specified map of variables. Template string can contain
+ * dynamic parts in the form of ${VARNAME}. Each such part is replaced with value of the
+ * variable if such exists in the map, or with empty string otherwise.
+ *
+ * @param template Template string
+ * @param variables Map of variables (can be null)
+ * @return Evaluated string
+ */
+ public static String evaluateTemplate(String template, Map variables) {
+ if (template == null) {
+ return template;
+ }
+
+ StringBuilder result = new StringBuilder();
+
+ int startIndex = template.indexOf(VAR_START);
+ int endIndex = -1;
+
+ while (startIndex >= 0 && startIndex < template.length()) {
+ result.append( template.substring(endIndex + 1, startIndex) );
+ endIndex = template.indexOf(VAR_END, startIndex);
+
+ if (endIndex > startIndex) {
+ String varName = template.substring(startIndex + VAR_START.length(), endIndex);
+ Object resultObj = variables != null ? variables.get(varName.toLowerCase()) : "";
+ result.append( resultObj == null ? "" : resultObj.toString() );
+ }
+
+ startIndex = template.indexOf( VAR_START, Math.max(endIndex + VAR_END.length(), startIndex + 1) );
+ }
+
+ result.append( template.substring(endIndex + 1) );
+
+ return result.toString();
+ }
+
+ public static String[] tokenize(String s, String delimiters) {
+ if (s == null) {
+ return new String[] {};
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer(s, delimiters);
+ String result[] = new String[tokenizer.countTokens()];
+ int index = 0;
+ while (tokenizer.hasMoreTokens()) {
+ result[index++] = tokenizer.nextToken();
+ }
+
+ return result;
+ }
+
+ public static void updateTagTransformations(CleanerTransformations transformations, String key, String value) {
+ int index = key.indexOf('.');
+
+ // new tag transformation case (tagname[=destname[,preserveatts]])
+ if (index <= 0) {
+ String destTag = null;
+ boolean preserveSourceAtts = true;
+ if (value != null) {
+ String[] tokens = tokenize(value, ",;");
+ if (tokens.length > 0) {
+ destTag = tokens[0];
+ }
+ if (tokens.length > 1) {
+ preserveSourceAtts = "true".equalsIgnoreCase(tokens[1]) ||
+ "yes".equalsIgnoreCase(tokens[1]) ||
+ "1".equals(tokens[1]);
+ }
+ }
+ TagTransformation newTagTrans = new TagTransformation(key, destTag, preserveSourceAtts);
+ transformations.addTransformation(newTagTrans);
+ } else { // attribute transformation description
+ String[] parts = tokenize(key, ".");
+ String tagName = parts[0];
+ TagTransformation trans = transformations.getTransformation(tagName);
+ if (trans != null) {
+ trans.addAttributeTransformation(parts[1], value);
+ }
+ }
+ }
+
+ /**
+ * Checks if specified link is full URL.
+ *
+ * @param link
+ * @return True, if full URl, false otherwise.
+ */
+ public static boolean isFullUrl(String link) {
+ if (link == null) {
+ return false;
+ }
+ link = link.trim().toLowerCase();
+ return link.startsWith("http://") || link.startsWith("https://") || link.startsWith("file://");
+ }
+
+ /**
+ * Calculates full URL for specified page URL and link
+ * which could be full, absolute or relative like there can
+ * be found in A or IMG tags.
+ */
+ public static String fullUrl(String pageUrl, String link) {
+ if (isFullUrl(link)) {
+ return link;
+ } else if (link != null && link.startsWith("?")) {
+ int qindex = pageUrl.indexOf('?');
+ int len = pageUrl.length();
+ if (qindex < 0) {
+ return pageUrl + link;
+ } else if (qindex == len - 1) {
+ return pageUrl.substring(0, len - 1) + link;
+ } else {
+ return pageUrl + "&" + link.substring(1);
+ }
+ }
+
+ boolean isLinkAbsolute = link.startsWith("/");
+
+ if (!isFullUrl(pageUrl)) {
+ pageUrl = "http://" + pageUrl;
+ }
+
+ int slashIndex = isLinkAbsolute ? pageUrl.indexOf("/", 8) : pageUrl.lastIndexOf("/");
+ if (slashIndex <= 8) {
+ pageUrl += "/";
+ } else {
+ pageUrl = pageUrl.substring(0, slashIndex + 1);
+ }
+
+ return isLinkAbsolute ? pageUrl + link.substring(1) : pageUrl + link;
+ }
+
+ /**
+ * @param name
+ * @return For xml element name or attribute name returns prefix (part before :) or null if there is no prefix
+ */
+ public static String getXmlNSPrefix(String name) {
+ int colIndex = name.indexOf(':');
+ if (colIndex > 0) {
+ return name.substring(0, colIndex);
+ }
+
+ return null;
+ }
+
+ /**
+ * @param name
+ * @return For xml element name or attribute name returns name after prefix (part after :)
+ */
+ public static String getXmlName(String name) {
+ int colIndex = name.indexOf(':');
+ if (colIndex > 0 && colIndex < name.length() - 1) {
+ return name.substring(colIndex + 1);
+ }
+
+ return name;
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/XPather.java.svn-base b/src/org/htmlcleaner/.svn/text-base/XPather.java.svn-base
new file mode 100644
index 0000000..7ec44fb
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/XPather.java.svn-base
@@ -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.*;
+
+/**
+ *
Utility for searching cleaned document tree with XPath expressions.
+ *
+ */
+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();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/XPatherException.java.svn-base b/src/org/htmlcleaner/.svn/text-base/XPatherException.java.svn-base
new file mode 100644
index 0000000..811478e
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/XPatherException.java.svn-base
@@ -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;
+
+
+/**
+ *
Exception that could occure during XPather evaluation.
+ */
+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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/.svn/text-base/XmlSerializer.java.svn-base b/src/org/htmlcleaner/.svn/text-base/XmlSerializer.java.svn-base
new file mode 100644
index 0000000..0b4ee47
--- /dev/null
+++ b/src/org/htmlcleaner/.svn/text-base/XmlSerializer.java.svn-base
@@ -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.*;
+
+/**
+ *
Abstract XML serializer - contains common logic for descendants.
+ */
+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 definedNSPrefixes = null;
+ Set additionalNSDeclNeeded = null;
+
+ String tagPrefix = Utils.getXmlNSPrefix(tagName);
+ if (tagPrefix != null) {
+ if (nsAware) {
+ definedNSPrefixes = new HashSet();
+ tagNode.collectNamespacePrefixesOnPath(definedNSPrefixes);
+ if ( !definedNSPrefixes.contains(tagPrefix) ) {
+ additionalNSDeclNeeded = new TreeSet();
+ additionalNSDeclNeeded.add(tagPrefix);
+ }
+ } else {
+ tagName = Utils.getXmlName(tagName);
+ }
+ }
+
+ writer.write("<" + tagName);
+
+ // write attributes
+ for (Map.Entry 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();
+ tagNode.collectNamespacePrefixesOnPath(definedNSPrefixes);
+ }
+ if ( !definedNSPrefixes.contains(attPrefix) ) {
+ if (additionalNSDeclNeeded == null) {
+ additionalNSDeclNeeded = new TreeSet();
+ }
+ additionalNSDeclNeeded.add(attPrefix);
+ }
+ } else {
+ attName = Utils.getXmlName(attName);
+ }
+ }
+ writer.write(" " + attName + "=\"" + escapeXml(entry.getValue()) + "\"");
+ }
+
+ // write namespace declarations
+ if (nsAware) {
+ Map nsDeclarations = tagNode.getNamespaceDeclarations();
+ if (nsDeclarations != null) {
+ for (Map.Entry 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(">");
+ }
+ }
+
+ 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");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/htmlcleaner/BaseToken.java b/src/org/htmlcleaner/BaseToken.java
new file mode 100644
index 0000000..200aae0
--- /dev/null
+++ b/src/org/htmlcleaner/BaseToken.java
@@ -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;
+
+/**
+ *
+ * Base token interface. Tokens are individual entities recognized by HTML
+ * parser.
+ *
+ */
+public interface BaseToken {
+ void serialize(Serializer serializer, Writer writer) throws IOException;
+}
diff --git a/src/org/htmlcleaner/BrowserCompactXmlSerializer.java b/src/org/htmlcleaner/BrowserCompactXmlSerializer.java
new file mode 100644
index 0000000..ee5e518
--- /dev/null
+++ b/src/org/htmlcleaner/BrowserCompactXmlSerializer.java
@@ -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;
+
+/**
+ *
+ * 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.
+ *
+ */
+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