From 10df74ac650499e949574a6ae1b0226634bef2f5 Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Tue, 31 Jan 2023 03:51:42 +0900 Subject: [PATCH 01/13] fix that supporting https for external links --- src/main/java/org/codavaj/process/docparser/ParserUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils.java b/src/main/java/org/codavaj/process/docparser/ParserUtils.java index 0fc8b3b..414b445 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils.java @@ -1919,7 +1919,7 @@ public void setExternalLinks(List externalLinks) { /** check an url (including local path) is exist or not */ private static boolean exists(String url) { URI uri; - if (!url.startsWith("http:")) { + if (!url.startsWith("http:") && !url.startsWith("https:")) { return Files.exists(Paths.get(url)); } else { try { @@ -1939,7 +1939,7 @@ private static boolean exists(String url) { */ private static InputSource getInputSource(String url) throws IOException { URI uri = null; - if (!url.startsWith("http:")) { + if (!url.startsWith("http:") && !url.startsWith("https:")) { uri = Paths.get(url).toUri(); } else { uri = URI.create(url); From 35e0abb62876180f97bbc7083620c5d20e39ecbc Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Tue, 31 Jan 2023 03:52:16 +0900 Subject: [PATCH 02/13] fix that when constant file is missing --- src/main/java/org/codavaj/process/docparser/ParserUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils.java b/src/main/java/org/codavaj/process/docparser/ParserUtils.java index 414b445..7665554 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils.java @@ -18,6 +18,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -56,6 +57,7 @@ import org.dom4j.tree.DefaultText; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import vavi.util.Debug; import static com.rainerhahnekamp.sneakythrow.Sneaky.sneaked; @@ -2077,6 +2079,8 @@ public void processConstant(Map maps, boolean lenient) throws IOEx Document allConstants = loadHtmlAsDom(getInputSource(allConstantsFilename)); determineConstants(allConstants, maps, lenient); + } catch (FileNotFoundException e) { +Debug.println("accept not found???"); // TODO } catch (SAXException e) { throw new IllegalStateException(e); } From f9084a9f9dc2914ea8b92b456fc6bc317429340d Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Tue, 31 Jan 2023 03:55:46 +0900 Subject: [PATCH 03/13] make program runner works well --- README.md | 73 +++++++++++++------ pom.xml | 69 +++++++++++++++--- .../commentator/JavaParserCommentator.java | 2 +- src/test/java/commentator/JdtCommentator.java | 2 +- .../java/commentator/RewriteCommentator.java | 2 +- .../java/commentator/SpoonCommentator.java | 2 +- 6 files changed, 113 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 5b1ff24..21fd528 100644 --- a/README.md +++ b/README.md @@ -3,34 +3,27 @@ [![CodeQL](https://github.com/umjammer/codavaj/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/umjammer/codavaj/actions/workflows/codeql-analysis.yml) ![Java](https://img.shields.io/badge/Java-8-b07219) -# CODAVAJ - ( javadoc in reverse ) README +# CODAVAJ ( javadoc in reverse ) ## Convert Javadoc to Java Source -to convert javadoc tree into java source (external references to Sun's -standard javadocs are automatically resolved +to convert javadoc tree into java source
+(external references to Sun's standard javadocs are automatically resolved i.e. `http://java.sun.com/j2se/1.5.0/docs/api/`) ``` codavaj.cmd codavaj {}* ``` -i.e. +### i.e. -``` -codavaj.cmd codavaj tmp/jumpi/javadoc tmp/jumpi/src -``` - -or - -``` -codavaj.cmd codavaj tmp/jumpi/javadoc tmp/jumpi/src http://external.link.com/api -``` - -or - -``` -codavaj.cmd codavaj http://jumpi.sourceforge.net/javadoc/j2se tmp/jumpi/src +```shell + $ # from LOCAL javadoc + $ codavaj.cmd codavaj tmp/jumpi/javadoc tmp/jumpi/src + $ # or from local javadoc w/ EXTERNAL LINK + $ codavaj.cmd codavaj tmp/jumpi/javadoc tmp/jumpi/src http://external.link.com/api + $ # or from REMOTE javadoc + $ codavaj.cmd codavaj http://jumpi.sourceforge.net/javadoc/j2se tmp/jumpi/src ``` ## Cooperate with Java Parser @@ -42,18 +35,52 @@ codavaj.cmd codavaj http://jumpi.sourceforge.net/javadoc/j2se tmp/jumpi/src | [JDT](https://www.eclipse.org/jdt/) | ✅ | đŸšĢ | [📄](https://github.com/umjammer/codavaj/blob/master/src/test/java/commentator/JgtCommentator.java) | little bit worse than JavaParser | | [spoon](https://github.com/INRIA/spoon) | ✅ | đŸšĢ | [📄](https://github.com/umjammer/codavaj/blob/master/src/test/java/commentator/SpoonCommentator.java) | formats are gone | +### Example + +```shell + $ # install codavaj + $ cd $HOME/src/java + $ git clone https://github.com/umjammer/codavaj + + $ # decompile by fernflower + $ cd $HOME/src/java/javax-speech + $ curl -o tmp/speech-1.0.0.jar https://github.com/umjammer/umjammer/raw/mvn-repo/javax/speech/speech/1.0.0/speech-1.0.0.jar + $ mkdir -p tmp/classes + $ java -jar fernflower.jar tmp/speech-1.0.0.jar tmp/classes + $ pushd src/main/java; jar xvf $HOME/src/java/javax-speech/tmp/classes/speech-1.0.0.jar; popd + + $ # add comment from javadoc on the net to decompiled source + $ cd $HOME/src/java/javax-speech + $ mkdir -p src/main/java + $ mkdir -p tmp/src + $ (pushd ~/src/java/codavaj; mvn -P comment test-compile antrun:run@javaParser \ + -Dcomment.javadoc=https://docs.oracle.com/cd/E17802_01/products/products/java-media/speech/forDevelopers/jsapi-doc \ + -Dcomment.source=$HOME/src/java/javax-speech/src/main/java \ + -Dcomment.out=$HOME/src/java/javax-speech/tmp/src \ + ; popd) + $ diff -rBw src/main/java tmp/src | less -R + $ diff -rBw src/main/java tmp/src > tmp/c.patch + $ pushd src/main/java; patch -p 2 < ../../../tmp/c.patch; popd +``` + ## known issues -* codavaj does not introduce default constructor's if they weren't found -in the javadoc. This leads to compile problems if there are subclasses -which use the class's default constructor through the implicit super(). + * codavaj does not introduce default constructor's if they weren't found + in the javadoc. This leads to compile problems if there are subclasses + which use the class's default constructor through the implicit super(). -* nekohtml ~1.19.22 + * nekohtml ~1.19.22 * https://mvnrepository.com/artifact/net.sourceforge.nekohtml/nekohtml/1.9.22 * https://mvnrepository.com/artifact/xerces/xercesImpl/2.11.0 * but 1.19.22 doesn't work with this project currently * so i excluded xerces from dependencies, and add xerces 2.12.2 individually. idk how codeql detect those. * https://sourceforge.net/p/nekohtml/bugs/167/#fdcc + + * @see split too much by arguments' separator "," + * override methods after 2nd.'s comment are gone + * h hags + * code tag + * ol, ul, li tag ## TODO @@ -62,4 +89,4 @@ which use the class's default constructor through the implicit super(). * ~~javadoc 11~~ * ~~en test case~~ * https://github.com/HtmlUnit/htmlunit-neko - * https://github.com/HtmlUnit/htmlunit-neko/security/advisories/GHSA-6jmm-mp6w-4rrg \ No newline at end of file + * https://github.com/HtmlUnit/htmlunit-neko/security/advisories/GHSA-6jmm-mp6w-4rrg diff --git a/pom.xml b/pom.xml index 9fc7bc7..dc2ef57 100644 --- a/pom.xml +++ b/pom.xml @@ -161,6 +161,10 @@ TODO comment + + https://docs.oracle.com/javase/jp/8/docs/api/ + (\w+)+ + @@ -176,6 +180,28 @@ TODO + + + + + + + + + + + + + + + + rewrite + + run + + + + @@ -186,11 +212,33 @@ TODO - - - - - + + + + + + + + + + + jdt + + run + + + + + + + + + + + + + + @@ -207,12 +255,13 @@ TODO + - - - - - + + + + + diff --git a/src/test/java/commentator/JavaParserCommentator.java b/src/test/java/commentator/JavaParserCommentator.java index 1e5436a..693e8d6 100644 --- a/src/test/java/commentator/JavaParserCommentator.java +++ b/src/test/java/commentator/JavaParserCommentator.java @@ -46,7 +46,7 @@ public class JavaParserCommentator { public TypeFactory analyze(String javadocdir, List externalLinks) throws Exception { Debug.println("analyze start: " + packageFilter); DocParser dp = new DocParser(); - dp.setJavadocClassName(packageFilter + "(\\.[\\w]+)*\\.[A-Z]\\w+$"); + dp.setJavadocClassName(packageFilter + "([\\w]+\\.)*[A-Z]\\w+$"); dp.setJavadocDirName(javadocdir); dp.setExternalLinks(externalLinks); dp.addProgressListener(System.err::println); diff --git a/src/test/java/commentator/JdtCommentator.java b/src/test/java/commentator/JdtCommentator.java index 288f5e6..4a49872 100644 --- a/src/test/java/commentator/JdtCommentator.java +++ b/src/test/java/commentator/JdtCommentator.java @@ -50,7 +50,7 @@ public class JdtCommentator { public TypeFactory analyze(String javadocdir, List externalLinks) throws Exception { Debug.println("analyze start: " + packageFilter); DocParser dp = new DocParser(); - dp.setJavadocClassName(packageFilter + "(\\.[\\w]+)*\\.[A-Z]\\w+$"); + dp.setJavadocClassName(packageFilter + "([\\w]+\\.)*[A-Z]\\w+$"); dp.setJavadocDirName(javadocdir); dp.setExternalLinks(externalLinks); dp.addProgressListener(System.err::println); diff --git a/src/test/java/commentator/RewriteCommentator.java b/src/test/java/commentator/RewriteCommentator.java index 0a4b471..37223e7 100644 --- a/src/test/java/commentator/RewriteCommentator.java +++ b/src/test/java/commentator/RewriteCommentator.java @@ -44,7 +44,7 @@ public class RewriteCommentator { public TypeFactory analyze(String javadocdir, List externalLinks) throws Exception { Debug.println("analyze start: " + packageFilter); DocParser dp = new DocParser(); - dp.setJavadocClassName(packageFilter + "(\\.[\\w]+)*\\.[A-Z]\\w+$"); + dp.setJavadocClassName(packageFilter + "([\\w]+\\.)*[A-Z]\\w+$"); dp.setJavadocDirName(javadocdir); dp.setExternalLinks(externalLinks); dp.addProgressListener(System.err::println); diff --git a/src/test/java/commentator/SpoonCommentator.java b/src/test/java/commentator/SpoonCommentator.java index 9e6502b..7fa5486 100644 --- a/src/test/java/commentator/SpoonCommentator.java +++ b/src/test/java/commentator/SpoonCommentator.java @@ -41,7 +41,7 @@ public class SpoonCommentator { public TypeFactory analyze(String javadocdir, List externalLinks) throws Exception { Debug.println("analyze start: " + packageFilter); DocParser dp = new DocParser(); - dp.setJavadocClassName(packageFilter + "(\\.[\\w]+)*\\.[A-Z]\\w+$"); + dp.setJavadocClassName(packageFilter + "([\\w]+\\.)*[A-Z]\\w+$"); dp.setJavadocDirName(javadocdir); dp.setExternalLinks(externalLinks); dp.addProgressListener(System.err::println); From ca6effb5b6dc85da8f4cd3bf54cbecc88b8d0e53 Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Wed, 5 Jun 2024 18:15:54 +0900 Subject: [PATCH 04/13] clean up --- .../docparser/FullyQualifiedNameMap.java | 2 -- .../process/docparser/ParseException.java | 2 -- .../process/docparser/ParserUtils10.java | 2 -- .../process/docparser/ParserUtils11.java | 2 -- .../process/docparser/ParserUtils12.java | 2 -- .../process/docparser/ParserUtils13.java | 2 -- .../process/docparser/ParserUtils8.java | 2 +- .../java/org/codavaj/type/Commentable.java | 2 -- .../java/{Testcase.java => TestCase.java} | 21 ++++++++++++------- .../commentator/JavaParserCommentator.java | 2 -- .../JavaParserCommentatorPrototype.java | 2 -- src/test/java/commentator/JdtCommentator.java | 2 -- .../java/commentator/RewriteCommentator.java | 2 -- .../java/commentator/SpoonCommentator.java | 2 -- src/test/java/vavi/test/codavaj/Test1.java | 2 -- src/test/java/vavi/test/codavaj/Test2.java | 2 -- src/test/java/vavi/test/codavaj/Test3.java | 2 -- src/test/java/vavi/test/codavaj/Test4.java | 2 -- src/test/java/vavi/test/codavaj/Test5.java | 2 -- 19 files changed, 15 insertions(+), 42 deletions(-) rename src/test/java/{Testcase.java => TestCase.java} (78%) diff --git a/src/main/java/org/codavaj/process/docparser/FullyQualifiedNameMap.java b/src/main/java/org/codavaj/process/docparser/FullyQualifiedNameMap.java index 66af38d..bdb3551 100644 --- a/src/main/java/org/codavaj/process/docparser/FullyQualifiedNameMap.java +++ b/src/main/java/org/codavaj/process/docparser/FullyQualifiedNameMap.java @@ -101,5 +101,3 @@ public String guess(String typeName) { } } } - -/* */ diff --git a/src/main/java/org/codavaj/process/docparser/ParseException.java b/src/main/java/org/codavaj/process/docparser/ParseException.java index c20a65b..ac650a3 100644 --- a/src/main/java/org/codavaj/process/docparser/ParseException.java +++ b/src/main/java/org/codavaj/process/docparser/ParseException.java @@ -49,5 +49,3 @@ public ParseException(String m, Throwable t) { super(m, t); } } - -/* */ diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils10.java b/src/main/java/org/codavaj/process/docparser/ParserUtils10.java index b8f7da5..87ca5c9 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils10.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils10.java @@ -85,5 +85,3 @@ public boolean isSuitableVersion(String version) { return versionComparator.compare(version, "10.0.0") >= 0 && versionComparator.compare(version, "11.0.0") < 0; } } - -/* */ diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils11.java b/src/main/java/org/codavaj/process/docparser/ParserUtils11.java index cefad58..e8bf284 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils11.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils11.java @@ -96,5 +96,3 @@ public String getFirstIndexFileName() { return "allclasses-index.html"; } } - -/* */ diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils12.java b/src/main/java/org/codavaj/process/docparser/ParserUtils12.java index 90ccc6a..11e0062 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils12.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils12.java @@ -43,5 +43,3 @@ public boolean isSuitableVersion(String version) { return versionComparator.compare(version, "12.0.0") >= 0 && versionComparator.compare(version, "13.0.0") < 0; } } - -/* */ diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils13.java b/src/main/java/org/codavaj/process/docparser/ParserUtils13.java index 495a496..beea4b5 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils13.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils13.java @@ -31,5 +31,3 @@ public boolean isSuitableVersion(String version) { return versionComparator.compare(version, "13.0.0") >= 0; } } - -/* */ diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils8.java b/src/main/java/org/codavaj/process/docparser/ParserUtils8.java index 3a7e2c6..7952596 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils8.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils8.java @@ -277,7 +277,7 @@ protected void extendedType(Type t, Document typeXml) { combinedText.append(convertNodesToString(n)); } String text = combinedText.toString().trim().replaceFirst("^.+\\s*" + keyword + "\\s+([\\w_\\$\\.\\<\\>]+)\\s*.*$", "$1"); - if (text.length() > 0) { + if (!text.isEmpty()) { String typeName = fqnm.toFullyQualifiedName(t, text); t.setSuperType(typeName); } diff --git a/src/main/java/org/codavaj/type/Commentable.java b/src/main/java/org/codavaj/type/Commentable.java index dc24285..34e3e1c 100644 --- a/src/main/java/org/codavaj/type/Commentable.java +++ b/src/main/java/org/codavaj/type/Commentable.java @@ -62,5 +62,3 @@ default Optional getInnerCommentAsString() { } } } - -/* */ diff --git a/src/test/java/Testcase.java b/src/test/java/TestCase.java similarity index 78% rename from src/test/java/Testcase.java rename to src/test/java/TestCase.java index 78f50c4..14532b3 100644 --- a/src/test/java/Testcase.java +++ b/src/test/java/TestCase.java @@ -7,6 +7,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Scanner; import java.util.zip.CRC32; import java.util.zip.Checksum; @@ -18,12 +19,12 @@ /** - * Testcase. + * TestCase. * * @author Naohide Sano (umjammer) * @version 0.00 2019/05/09 umjammer initial version
*/ -class Testcase { +class TestCase { @ParameterizedTest @CsvSource({ @@ -65,15 +66,21 @@ void test(String version, String expected) throws Exception { System.err.println(path1 + ", " + path2 + " (" + version + ")"); System.err.println(checksum1.getValue() + ", " + checksum2.getValue()); - ProcessBuilder pb = new ProcessBuilder().command("diff", "-ru", "-x", ".DS_Store", "src/test/resources/codavaj/" + expected, "tmp/test/" + version); + ProcessBuilder pb = new ProcessBuilder().inheritIO().redirectErrorStream(true) + .command("diff", "-ru", "-x", ".DS_Store", "src/test/resources/codavaj/" + expected, "tmp/test/" + version); System.err.println(String.join(" ", pb.command())); pb.inheritIO(); - pb.start(); + Process p = pb.start(); + p.waitFor(); + + try (Scanner s = new Scanner(p.getInputStream())) { + while (s.hasNextLine()) { + System.err.println(s.nextLine()); + } + } } - assertEquals(checksum1.getValue(), checksum2.getValue()); + assertEquals(checksum1.getValue(), checksum2.getValue(), version); } } } - -/* */ diff --git a/src/test/java/commentator/JavaParserCommentator.java b/src/test/java/commentator/JavaParserCommentator.java index 693e8d6..a3690f5 100644 --- a/src/test/java/commentator/JavaParserCommentator.java +++ b/src/test/java/commentator/JavaParserCommentator.java @@ -233,5 +233,3 @@ String getSignatureString(ConstructorDeclaration n) { } } } - -/* */ diff --git a/src/test/java/commentator/JavaParserCommentatorPrototype.java b/src/test/java/commentator/JavaParserCommentatorPrototype.java index 3849025..290483d 100644 --- a/src/test/java/commentator/JavaParserCommentatorPrototype.java +++ b/src/test/java/commentator/JavaParserCommentatorPrototype.java @@ -235,5 +235,3 @@ String getSignatureString(ConstructorDeclaration n) { } } } - -/* */ diff --git a/src/test/java/commentator/JdtCommentator.java b/src/test/java/commentator/JdtCommentator.java index 4a49872..a2519c5 100644 --- a/src/test/java/commentator/JdtCommentator.java +++ b/src/test/java/commentator/JdtCommentator.java @@ -229,5 +229,3 @@ String getSignatureString(MethodDeclaration n) { } } } - -/* */ diff --git a/src/test/java/commentator/RewriteCommentator.java b/src/test/java/commentator/RewriteCommentator.java index 37223e7..5c14b03 100644 --- a/src/test/java/commentator/RewriteCommentator.java +++ b/src/test/java/commentator/RewriteCommentator.java @@ -164,5 +164,3 @@ String getSignatureString(com.netflix.rewrite.ast.Type t) { } } } - -/* */ diff --git a/src/test/java/commentator/SpoonCommentator.java b/src/test/java/commentator/SpoonCommentator.java index 7fa5486..c4b3b3d 100644 --- a/src/test/java/commentator/SpoonCommentator.java +++ b/src/test/java/commentator/SpoonCommentator.java @@ -150,5 +150,3 @@ String getSignatureString(CtConstructor n) { } } } - -/* */ diff --git a/src/test/java/vavi/test/codavaj/Test1.java b/src/test/java/vavi/test/codavaj/Test1.java index e570ae2..5db0177 100644 --- a/src/test/java/vavi/test/codavaj/Test1.java +++ b/src/test/java/vavi/test/codavaj/Test1.java @@ -72,5 +72,3 @@ public Object method1(Object arg1, Object arg2) throws IllegalStateException, Il return null; } } - -/* */ diff --git a/src/test/java/vavi/test/codavaj/Test2.java b/src/test/java/vavi/test/codavaj/Test2.java index ae0f689..b4e61bc 100644 --- a/src/test/java/vavi/test/codavaj/Test2.java +++ b/src/test/java/vavi/test/codavaj/Test2.java @@ -46,5 +46,3 @@ public Test3 method1(T arg1, String...arg2) throws IOException { public void method5() { } } - -/* */ diff --git a/src/test/java/vavi/test/codavaj/Test3.java b/src/test/java/vavi/test/codavaj/Test3.java index d7bb9c4..e6d8366 100644 --- a/src/test/java/vavi/test/codavaj/Test3.java +++ b/src/test/java/vavi/test/codavaj/Test3.java @@ -33,5 +33,3 @@ public enum Test3 { Test3(int i) { } } - -/* */ diff --git a/src/test/java/vavi/test/codavaj/Test4.java b/src/test/java/vavi/test/codavaj/Test4.java index 0e8032b..26cd641 100644 --- a/src/test/java/vavi/test/codavaj/Test4.java +++ b/src/test/java/vavi/test/codavaj/Test4.java @@ -30,5 +30,3 @@ */ int field2() default 10; } - -/* */ diff --git a/src/test/java/vavi/test/codavaj/Test5.java b/src/test/java/vavi/test/codavaj/Test5.java index 3f02326..4941a55 100644 --- a/src/test/java/vavi/test/codavaj/Test5.java +++ b/src/test/java/vavi/test/codavaj/Test5.java @@ -23,5 +23,3 @@ public interface Test5 { void method5(); } - -/* */ From 0185961545f6bf830ce5bfcb4f03864dd17edb0d Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Fri, 6 Feb 2026 18:48:59 +0900 Subject: [PATCH 05/13] catch up w/ dependencies update --- .../commentator/JavaParserCommentator.java | 17 ++- .../JavaParserCommentatorPrototype.java | 23 ++-- .../java/commentator/RewriteCommentator.java | 118 +++++++++--------- .../java/commentator/SpoonCommentator.java | 14 +-- 4 files changed, 86 insertions(+), 86 deletions(-) diff --git a/src/test/java/commentator/JavaParserCommentator.java b/src/test/java/commentator/JavaParserCommentator.java index a3690f5..327d852 100644 --- a/src/test/java/commentator/JavaParserCommentator.java +++ b/src/test/java/commentator/JavaParserCommentator.java @@ -13,10 +13,6 @@ import java.util.Collections; import java.util.List; -import org.codavaj.process.docparser.DocParser; -import org.codavaj.type.Type; -import org.codavaj.type.TypeFactory; - import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; @@ -24,10 +20,13 @@ import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; -import com.github.javaparser.ast.comments.JavadocComment; +import com.github.javaparser.ast.comments.TraditionalJavadocComment; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.utils.Pair; import com.google.common.collect.Streams; +import org.codavaj.process.docparser.DocParser; +import org.codavaj.type.Type; +import org.codavaj.type.TypeFactory; import vavi.util.Debug; @@ -106,7 +105,7 @@ public void visit(ClassOrInterfaceDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(t.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(t.getInnerCommentAsString().get())); System.err.println("RC: " + "CLASS: " + n.getNameAsString()); })); super.visit(n, arg); @@ -128,7 +127,7 @@ public void visit(FieldDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(f.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(f.getInnerCommentAsString().get())); System.err.println("RC: " + "FIELD: " + v.getNameAsString()); })); } else { @@ -155,7 +154,7 @@ public void visit(MethodDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(m.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(m.getInnerCommentAsString().get())); System.err.println("RC: " + "METHOD: " + getSignatureString(n)); }); @@ -181,7 +180,7 @@ public void visit(ConstructorDeclaration n, Void arg) { type.getType(((ClassOrInterfaceDeclaration) n.getParentNode().get()).getNameAsString()).flatMap(t -> t.getMethod(getSignatureString(n))).ifPresent(m -> { m.getCommentAsString().ifPresent(s -> { - n.setComment(new JavadocComment(m.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(m.getInnerCommentAsString().get())); System.err.println("RC: " + "CONSTRUCTOR: " + getSignatureString(n)); }); diff --git a/src/test/java/commentator/JavaParserCommentatorPrototype.java b/src/test/java/commentator/JavaParserCommentatorPrototype.java index 290483d..d9965fa 100644 --- a/src/test/java/commentator/JavaParserCommentatorPrototype.java +++ b/src/test/java/commentator/JavaParserCommentatorPrototype.java @@ -12,10 +12,6 @@ import java.util.ArrayList; import java.util.List; -import org.codavaj.process.docparser.DocParser; -import org.codavaj.type.Type; -import org.codavaj.type.TypeFactory; - import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; @@ -24,8 +20,11 @@ import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; -import com.github.javaparser.ast.comments.JavadocComment; +import com.github.javaparser.ast.comments.TraditionalJavadocComment; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import org.codavaj.process.docparser.DocParser; +import org.codavaj.type.Type; +import org.codavaj.type.TypeFactory; /** @@ -100,7 +99,7 @@ public void visit(ClassOrInterfaceDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(t.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(t.getInnerCommentAsString().get())); System.err.println("RC: " + "CLASS: " + n.getNameAsString()); })); super.visit(n, arg); @@ -122,7 +121,7 @@ public void visit(FieldDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(f.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(f.getInnerCommentAsString().get())); System.err.println("RC: " + "FIELD: " + v.getName()); })); } else if (n.getParentNode().get() instanceof EnumDeclaration) { @@ -132,7 +131,7 @@ public void visit(FieldDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(f.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(f.getInnerCommentAsString().get())); System.err.println("RC: " + "ENUM: " + v.getName()); })); } @@ -156,7 +155,7 @@ public void visit(MethodDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(m.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(m.getInnerCommentAsString().get())); System.err.println("RC: " + "METHOD: " + getSignatureString(n)); })); } else if (n.getParentNode().get() instanceof EnumDeclaration) { @@ -165,7 +164,7 @@ public void visit(MethodDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(m.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(m.getInnerCommentAsString().get())); System.err.println("RC: " + "METHOD: " + getSignatureString(n)); })); } @@ -188,7 +187,7 @@ public void visit(ConstructorDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(m.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(m.getInnerCommentAsString().get())); System.err.println("RC: " + "CONSTRUCTOR: " + getSignatureString(n)); })); } else if (n.getParentNode().get() instanceof EnumDeclaration) { @@ -197,7 +196,7 @@ public void visit(ConstructorDeclaration n, Void arg) { // System.out.println("NEW:"); // System.out.println(s); - n.setComment(new JavadocComment(m.getInnerCommentAsString().get())); + n.setComment(new TraditionalJavadocComment(m.getInnerCommentAsString().get())); System.err.println("RC: " + "CONSTRUCTOR: " + getSignatureString(n)); })); } diff --git a/src/test/java/commentator/RewriteCommentator.java b/src/test/java/commentator/RewriteCommentator.java index 5c14b03..89cc879 100644 --- a/src/test/java/commentator/RewriteCommentator.java +++ b/src/test/java/commentator/RewriteCommentator.java @@ -18,14 +18,14 @@ import org.codavaj.type.Type; import org.codavaj.type.TypeFactory; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + import com.google.common.collect.Streams; -import com.netflix.rewrite.ast.Tr.CompilationUnit; -import com.netflix.rewrite.ast.Tr.Empty; -import com.netflix.rewrite.ast.Tr.MethodDecl; -import com.netflix.rewrite.ast.Tr.VariableDecls; -import com.netflix.rewrite.ast.visitor.AstVisitor; -import com.netflix.rewrite.parse.OracleJdkParser; -import com.netflix.rewrite.parse.Parser; import vavi.util.Debug; @@ -78,89 +78,91 @@ void exec(String javadocDir, String externalLink, String sourceDir, String outpu TypeFactory tf = analyze(javadocDir, el); - Parser parser = new OracleJdkParser(); + JavaParser parser = JavaParser.fromJavaVersion().build(); + ExecutionContext ctx = new InMemoryExecutionContext(t -> Debug.println(t.getMessage())); for (Type type : tf.getTypes()) { - Path sourcePath = Paths.get(sourceDir, type.getSourceFilename()); if (!Files.exists(sourcePath)) { -System.err.println("SK: " + sourcePath); + System.err.println("SK: " + sourcePath); continue; } - CompilationUnit unit = parser.parse(new String(Files.readAllBytes(sourcePath))); - - new AstVisitor((Void) null) { + List cus = parser.parse(Files.readString(sourcePath)) + .map(J.CompilationUnit.class::cast) + .toList(); + if (cus.isEmpty()) continue; + J.CompilationUnit unit = cus.get(0); + unit = (J.CompilationUnit) new JavaVisitor() { @Override - public Void visitMethod(MethodDecl n) { - - type.getMethod(getSignatureString(n)).ifPresent(m -> Streams.zip(m.getParameterList().stream(), n.getParams().getParams().stream(), - Pair::new - ).filter(p -> { - if (p.b instanceof Empty) { - return false; - } else if (p.b instanceof VariableDecls) { - // parameter must have one variable - String name = ((VariableDecls) p.b).getVars().get(0).getSimpleName(); - return !p.a.getName().equals(name); - } else { -System.err.println("?1: " + p.b); - return false; - } - }).forEach(p -> { - String name = ((VariableDecls) p.b).getVars().get(0).getSimpleName(); -System.err.println("RN: " + "PARAM: " + name + " -> " + p.a.getName() + " \t\t/ " + getSignatureString(n)); - // TODO this only rename a parameter name... - String diff = unit.refactor().changeFieldName((VariableDecls) p.b, p.a.getName()).diff(); -System.out.println(diff); - })); - - return null; + public J visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext p) { + J.MethodDeclaration mDecl = (J.MethodDeclaration) super.visitMethodDeclaration(method, p); + + type.getMethod(getSignatureString(mDecl)).ifPresent(m -> Streams.zip( + m.getParameterList().stream(), + mDecl.getParameters().stream(), + Pair::new + ).filter(pair -> { + if (pair.b instanceof J.Empty) { + return false; + } else if (pair.b instanceof J.VariableDeclarations) { + String name = ((J.VariableDeclarations) pair.b).getVariables().get(0).getSimpleName(); + return !pair.a.getName().equals(name); + } else { + System.err.println("?1: " + pair.b); + return false; + } + }).forEach(pair -> { + String oldName = ((J.VariableDeclarations) pair.b).getVariables().get(0).getSimpleName(); + System.err.println("RN: " + "PARAM: " + oldName + " -> " + pair.a.getName() + " \t\t/ " + getSignatureString(mDecl)); + + // Note: In OpenRewrite, renaming a parameter usually involves a separate RenameVariable visitor + // to update all references in the method body. + })); + + return mDecl; } /** */ - String getSignatureString(MethodDecl n) { - StringBuilder sb = new StringBuilder(n.getName().getSimpleName()); + String getSignatureString(J.MethodDeclaration n) { + StringBuilder sb = new StringBuilder(n.getSimpleName()); sb.append("("); - n.getParams().getParams().forEach(p -> { -//System.err.println("MP: "+ p); - if (p instanceof Empty) { - } else if (p instanceof VariableDecls) { - com.netflix.rewrite.ast.Type t = ((VariableDecls) p).getTypeExpr().getType(); - sb.append(getSignatureString(t)); + n.getParameters().forEach(p -> { + if (p instanceof J.VariableDeclarations) { + JavaType t = ((J.VariableDeclarations) p).getType(); + sb.append(getSignatureStringFromJavaType(t)); } }); sb.append(")"); - if (n.getReturnTypeExpr() != null) { // constructor -//System.err.println("MR: "+ n.getReturnTypeExpr()); - com.netflix.rewrite.ast.Type t = n.getReturnTypeExpr().getType(); - sb.append(getSignatureString(t)); + if (n.getReturnTypeExpression() != null) { + JavaType t = n.getReturnTypeExpression().getType(); + sb.append(getSignatureStringFromJavaType(t)); } -//System.err.println("SG: "+ sb.toString()); return sb.toString(); } /** */ - String getSignatureString(com.netflix.rewrite.ast.Type t) { + String getSignatureStringFromJavaType(JavaType t) { String name = null; - if (t instanceof com.netflix.rewrite.ast.Type.Primitive) { - name = ((com.netflix.rewrite.ast.Type.Primitive) t).getKeyword(); - } else if (t instanceof com.netflix.rewrite.ast.Type.Class c) { + if (t instanceof JavaType.Primitive) { + name = ((JavaType.Primitive) t).getKeyword(); + } else if (t instanceof JavaType.FullyQualified) { + JavaType.FullyQualified c = (JavaType.FullyQualified) t; name = c.getFullyQualifiedName(); } else { -System.err.println("?2: " + t); + System.err.println("?2: " + t); } return Type.getSignatureString(name); } - }.visit(unit); + }.visit(unit, ctx); Path result = Paths.get(outputDir, type.getSourceFilename()); if (!Files.exists(result.getParent())) { Files.createDirectories(result.getParent()); } -System.err.println("WR: "+ result); - Files.write(result, unit.print().getBytes()); + System.err.println("WR: " + result); + Files.write(result, unit.printAll().getBytes()); } } } diff --git a/src/test/java/commentator/SpoonCommentator.java b/src/test/java/commentator/SpoonCommentator.java index c4b3b3d..e5ab30c 100644 --- a/src/test/java/commentator/SpoonCommentator.java +++ b/src/test/java/commentator/SpoonCommentator.java @@ -13,8 +13,8 @@ import java.util.Collections; import java.util.List; +import com.github.javaparser.utils.Pair; import com.google.common.collect.Streams; -import kotlin.Pair; import org.codavaj.process.docparser.DocParser; import org.codavaj.type.Type; import org.codavaj.type.TypeFactory; @@ -96,9 +96,9 @@ public void process(CtMethod element) { type.getType(((CtClassImpl) element.getParent()).getSimpleName()).ifPresent(t -> { System.err.println("CM: METHOD: " + getSignatureString(element)); t.getMethod(getSignatureString(element)).ifPresent(m -> Streams.zip(m.getParameterList().stream(), element.getParameters().stream(), Pair::new) - .filter(p -> !p.getFirst().getName().equals(p.getSecond().getSimpleName())).forEach(p -> { -System.err.println("RN: " + "PARAM: " + p.getSecond().getSimpleName() + " -> " + p.getFirst().getName() + " \t\t/ " + getSignatureString(element)); - p.getSecond().setSimpleName(p.getFirst().getName()); // TODO this is not refactoring + .filter(p -> !p.a.getName().equals(p.b.getSimpleName())).forEach(p -> { +System.err.println("RN: " + "PARAM: " + p.b.getSimpleName() + " -> " + p.a.getName() + " \t\t/ " + getSignatureString(element)); + p.b.setSimpleName(p.a.getName()); // TODO this is not refactoring })); }); } @@ -122,9 +122,9 @@ public void process(CtConstructor element) { type.getType(((CtClassImpl) element.getParent()).getSimpleName()).ifPresent(t -> { System.err.println("CM: CONSTRUCTOR: " + getSignatureString(element)); t.getMethod(getSignatureString(element)).ifPresent(m -> Streams.zip(m.getParameterList().stream(), element.getParameters().stream(), Pair::new) - .filter(p -> !p.getFirst().getName().equals(p.getSecond().getSimpleName())).forEach(p -> { -System.err.println("RN: " + "PARAM: " + p.getSecond().getSimpleName() + " -> " + p.getFirst().getName() + " \t\t/ " + getSignatureString(element)); - p.getSecond().setSimpleName(p.getFirst().getName()); // TODO this is not refactoring + .filter(p -> !p.a.getName().equals(p.b.getSimpleName())).forEach(p -> { +System.err.println("RN: " + "PARAM: " + p.b.getSimpleName() + " -> " + p.a.getName() + " \t\t/ " + getSignatureString(element)); + p.b.setSimpleName(p.a.getName()); // TODO this is not refactoring })); }); } From 273cd2f6ee2fa2a458e559ee2dd3d50e4d5c7215 Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Fri, 6 Feb 2026 18:51:44 +0900 Subject: [PATCH 06/13] [@see] take snapshot --- .../process/docparser/ParserUtils.java | 94 ++++++++++++++++++- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/codavaj/process/docparser/ParserUtils.java b/src/main/java/org/codavaj/process/docparser/ParserUtils.java index 7665554..b83f93e 100644 --- a/src/main/java/org/codavaj/process/docparser/ParserUtils.java +++ b/src/main/java/org/codavaj/process/docparser/ParserUtils.java @@ -36,6 +36,7 @@ import java.util.ResourceBundle; import java.util.ServiceLoader; import java.util.StringTokenizer; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; @@ -192,9 +193,46 @@ protected void processDD(Type t, Node dd,String tag, List commentText) { logger.fine("ignore 3: " + dd.asXML()); return; } - replaceA(((Element) dd), true); // TODO no need to replace? + replaceAforSee(((Element) dd)); text = tidyText(dd, true); - break; +Debug.printf("--- %s", text); + +//--- somehow works +// String[] lines = text.split(", "); +// Arrays.stream(lines).forEach(l -> commentText.add("@see " + l)); +//--- + +//--- problems --- + StringBuilder stack = new StringBuilder(); + AtomicBoolean methodArgumentsStart = new AtomicBoolean(); + String[] lines = text.split(", "); // TODO split method argument separator also + Arrays.stream(lines).forEach(l -> { +Debug.printf("@@@: %s, %s, %s", l, methodArgumentsStart.get(), stack); + if (l.contains("(")) { + if (!l.contains(")")) { + assert stack.isEmpty() : "stack is not empty"; + assert !methodArgumentsStart.get() : "flag is true"; + methodArgumentsStart.set(true); + stack.append(l); + return; + } + } + if (methodArgumentsStart.get()) { + assert !stack.isEmpty() : "stack is empty"; + stack.append(l); + return; + } + if (l.contains(")")) { + stack.append(l); + commentText.add("@see " + stack); + stack.setLength(0); + methodArgumentsStart.set(false); + return; + } + commentText.add("@see " + l); + }); +//--- problems --- + return; case "return": replaceA(((Element) dd), true); text = tidyText(dd, true); @@ -224,7 +262,7 @@ private int processDT(Type t, List nodes, int j, String tag, List return j; } - /** Processes A */ + /** A tag to "@link" */ private String processA(Node a) { logger.fine("A: " + a.asXML()); String href = a.valueOf("@href"); @@ -237,6 +275,19 @@ private String processA(Node a) { } } + /** A tag to for "@see" */ + private String processAforSee(Node a) { +logger.fine("A: " + a.asXML()); + String href = a.valueOf("@href"); + if ((href.startsWith("http") || href.startsWith("../")) && + href.replace(".html#", ".").contains(a.getText().replaceAll("\\([\\w$_\\.,\\s\\[\\]]*\\)", ""))) { + String link = hrefToLink(href); + return link.replace("$", "."); + } else { + return href; + } + } + /** * @param url javadoc link * @return a class name with field or method @@ -360,7 +411,8 @@ protected List replaceA(Element element, boolean recursive) { logger.finer("before: " + before.asXML()); if (before.getNodeType() == Node.TEXT_NODE && ( before.getText().contains(rb.getString("token.comment.exclude.1")) || - before.getText().contains(rb.getString("token.comment.exclude.2")))) { + before.getText().contains(rb.getString("token.comment.exclude.2")) + )) { ignore = true; logger.finer("ignore 1.0: " + before.getText() + node.asXML()); } @@ -373,8 +425,40 @@ protected List replaceA(Element element, boolean recursive) { } } else { if (recursive) { - replaceA(((Element) node), true); + replaceA((Element) node, true); + } + } + } + } + return nodes; + } + + /** replace a tag fot @see tag */ + protected List replaceAforSee(Element element) { + List nodes = element.content(); + for (Node node : nodes) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + boolean ignore = false; + if ("A".equals(node.getName())) { + int index = nodes.indexOf(node); + logger.finer("index: " + index); + if (index > 0) { + Node before = nodes.get(index - 1); + logger.finer("before: " + before.asXML()); + if (before.getNodeType() == Node.TEXT_NODE && ( + before.getText().contains(rb.getString("token.comment.exclude.1")) || + before.getText().contains(rb.getString("token.comment.exclude.2")) + )) { + ignore = true; + logger.finer("ignore 1.0: " + before.getText() + node.asXML()); + } + } + if (!ignore) { + String string = processAforSee(node); + nodes.set(nodes.indexOf(node), new DefaultText(string)); } + } else { + replaceAforSee((Element) node); } } } From 935f37f7dc7c6ae1c48a0bc5558ed00bda724a0a Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Fri, 6 Feb 2026 18:52:16 +0900 Subject: [PATCH 07/13] update settings --- .github/workflows/codeql-analysis.yml | 14 +++-- .github/workflows/maven.yml | 4 +- README.md | 34 +++++++--- jitpack.yml | 3 - pom.xml | 89 +++++++++++---------------- src/main/resources/token.properties | 1 - 6 files changed, 72 insertions(+), 73 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1f01cc9..898e9d8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,6 +18,10 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write strategy: fail-fast: false @@ -30,11 +34,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -43,7 +47,7 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Setup Java JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' @@ -52,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,4 +70,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 868a967..1793071 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -9,14 +9,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check w/o SNAPSHOT when "bump version" if: ${{ contains(github.event.head_commit.message, 'bump version') }} run: grep "" pom.xml | head -1 | grep -v SNAPSHOT - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' diff --git a/README.md b/README.md index 21fd528..9d4764f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ [![Releases](https://jitpack.io/v/umjammer/codavaj.svg)](https://jitpack.io/#umjammer/codavaj) -[![Actions Status](https://github.com/umjammer/codavaj/actions/workflows/maven.yml/badge.svg)](https://github.com/umjammer/codavaj/actions) +[![Java CI](https://github.com/umjammer/codavaj/actions/workflows/maven.yml/badge.svg)](https://github.com/umjammer/codavaj/actions/workflows/maven.yml) [![CodeQL](https://github.com/umjammer/codavaj/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/umjammer/codavaj/actions/workflows/codeql-analysis.yml) -![Java](https://img.shields.io/badge/Java-8-b07219) +![Java](https://img.shields.io/badge/Java-17-b07219) -# CODAVAJ ( javadoc in reverse ) +# CODAVAJ -## Convert Javadoc to Java Source +logo        codavaj is anadrome of javadoc + +## Install + +* https://jitpack.io/#umjammer/codavaj + +## Usage + +### Convert Javadoc to Java Source to convert javadoc tree into java source
(external references to Sun's standard javadocs are automatically resolved @@ -15,7 +23,7 @@ i.e. `http://java.sun.com/j2se/1.5.0/docs/api/`) codavaj.cmd codavaj {}* ``` -### i.e. +#### i.e. ```shell $ # from LOCAL javadoc @@ -26,7 +34,7 @@ codavaj.cmd codavaj {}* $ codavaj.cmd codavaj http://jumpi.sourceforge.net/javadoc/j2se tmp/jumpi/src ``` -## Cooperate with Java Parser +### Cooperate with Java Parser | **parser** | **set javadoc to (decompiled) source** | **rename argument names as javadoc documented** | **code** | **output** | |:-----------|:--------------------------------------:|:-----------------------------------------------:|------------------------------------------------------------------------------------------------------------|:--------------------------------:| @@ -35,7 +43,7 @@ codavaj.cmd codavaj {}* | [JDT](https://www.eclipse.org/jdt/) | ✅ | đŸšĢ | [📄](https://github.com/umjammer/codavaj/blob/master/src/test/java/commentator/JgtCommentator.java) | little bit worse than JavaParser | | [spoon](https://github.com/INRIA/spoon) | ✅ | đŸšĢ | [📄](https://github.com/umjammer/codavaj/blob/master/src/test/java/commentator/SpoonCommentator.java) | formats are gone | -### Example +#### Example ```shell $ # install codavaj @@ -53,7 +61,7 @@ codavaj.cmd codavaj {}* $ cd $HOME/src/java/javax-speech $ mkdir -p src/main/java $ mkdir -p tmp/src - $ (pushd ~/src/java/codavaj; mvn -P comment test-compile antrun:run@javaParser \ + $ (pushd $HOME/src/java/codavaj; mvn -P comment test-compile antrun:run@javaParser \ -Dcomment.javadoc=https://docs.oracle.com/cd/E17802_01/products/products/java-media/speech/forDevelopers/jsapi-doc \ -Dcomment.source=$HOME/src/java/javax-speech/src/main/java \ -Dcomment.out=$HOME/src/java/javax-speech/tmp/src \ @@ -63,7 +71,7 @@ codavaj.cmd codavaj {}* $ pushd src/main/java; patch -p 2 < ../../../tmp/c.patch; popd ``` -## known issues +### known issues * codavaj does not introduce default constructor's if they weren't found in the javadoc. This leads to compile problems if there are subclasses @@ -82,6 +90,10 @@ codavaj.cmd codavaj {}* * code tag * ol, ul, li tag +## References + + * https://docs.openrewrite.org/ + ## TODO * ~~javadoc 1.8~~ @@ -90,3 +102,7 @@ codavaj.cmd codavaj {}* * ~~en test case~~ * https://github.com/HtmlUnit/htmlunit-neko * https://github.com/HtmlUnit/htmlunit-neko/security/advisories/GHSA-6jmm-mp6w-4rrg + TODO + * use vavi-util-screenscraping? + * interface default + * ~~Spoon~~ \ No newline at end of file diff --git a/jitpack.yml b/jitpack.yml index 4a7f508..efde7bf 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,5 +1,2 @@ jdk: - openjdk17 -before_install: - - sdk install java 17.0.1-open - - sdk use java 17.0.1-open diff --git a/pom.xml b/pom.xml index dc2ef57..8549e86 100644 --- a/pom.xml +++ b/pom.xml @@ -6,12 +6,7 @@ 1.4.6 -TODO - - use vavi-util-screenscraping? - interface default - - Spoon + https://github.com/umjammer/codavaj https://github.com/umjammer/codavaj @@ -110,7 +105,7 @@ TODO org.codehaus.mojo exec-maven-plugin - 3.0.0 + 3.1.1 retrieve-config @@ -129,7 +124,7 @@ TODO org.codehaus.mojo properties-maven-plugin - 1.0.0 + 1.2.1 read-properties @@ -149,13 +144,15 @@ TODO
+
@@ -279,10 +276,9 @@ TODO org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.12.1 - 17 - 17 + 17 @@ -307,7 +303,7 @@ TODO org.apache.maven.plugins maven-javadoc-plugin - 3.1.0 + 3.5.0 -J-Duser.language=en_US en_US @@ -320,9 +316,9 @@ TODO org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M7 + 3.2.5 - once + 1 -Xms2048m -Xmx4096m -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties @@ -345,7 +341,7 @@ TODO org.junit junit-bom - 5.9.1 + 5.14.1 pom import @@ -359,33 +355,19 @@ TODO 2.12.2 - org.httpunit + com.github.hazendaz.httpunit httpunit - 1.7.3 - - - nekohtml - nekohtml - - - xerces - xmlParserAPIs - - - xerces - xercesImpl - - + 2.5.0 org.dom4j dom4j - 2.1.3 + 2.2.0 net.sourceforge.nekohtml nekohtml - 1.9.16 + 1.9.16 xerces @@ -396,7 +378,7 @@ TODO org.apache.ant ant - 1.10.12 + 1.10.15 com.github.jaxen-xpath @@ -411,7 +393,7 @@ TODO com.github.umjammer vavi-commons - 1.1.8 + 1.1.16 @@ -432,45 +414,46 @@ TODO com.github.javaparser - javaparser-symbol-solver-core - 3.24.7 + javaparser-core + 3.28.0 test - com.netflix.devinsight.rewrite + org.openrewrite rewrite-core - 1.4.0 + 8.72.6 test - - - com.fasterxml.jackson.core - jackson-annotations - - - org.slf4j - slf4j-api - - + + + org.openrewrite + rewrite-java + 8.72.6 + test + + + com.google.guava + guava + 33.5.0-jre org.slf4j slf4j-jdk14 - 2.0.3 + 2.0.16 test org.eclipse.jdt org.eclipse.jdt.core - 3.29.0 + 3.44.0 test fr.inria.gforge.spoon spoon-core - 10.2.0 + 11.3.1-beta-6 test diff --git a/src/main/resources/token.properties b/src/main/resources/token.properties index ed4944e..e9f1a1f 100644 --- a/src/main/resources/token.properties +++ b/src/main/resources/token.properties @@ -37,4 +37,3 @@ token.see.exclude.2=Constant Field Values token.comment.exclude.1=Description copied from interface token.comment.exclude.2=Description copied from class token.deprecated=Deprecated -token.overrides=Overrides From 0ca219e6adeddeccfca94f99feaf59b832e08995 Mon Sep 17 00:00:00 2001 From: Naohide Sano Date: Sat, 4 Jul 2026 07:56:32 +0900 Subject: [PATCH 08/13] [nekohtml] import source tree into this project and adjust it (by fable5) --- pom.xml | 12 +- .../org/cyberneko/html/HTMLAugmentations.java | 134 + .../org/cyberneko/html/HTMLComponent.java | 51 + .../org/cyberneko/html/HTMLConfiguration.java | 702 +++ .../java/org/cyberneko/html/HTMLElements.java | 795 ++++ .../java/org/cyberneko/html/HTMLEntities.java | 143 + .../org/cyberneko/html/HTMLErrorReporter.java | 56 + .../org/cyberneko/html/HTMLEventInfo.java | 120 + .../java/org/cyberneko/html/HTMLScanner.java | 3841 +++++++++++++++++ .../org/cyberneko/html/HTMLTagBalancer.java | 1452 +++++++ .../html/HTMLTagBalancingListener.java | 45 + .../java/org/cyberneko/html/LostText.java | 83 + .../org/cyberneko/html/ObjectFactory.java | 509 +++ .../org/cyberneko/html/SecuritySupport.java | 118 + .../org/cyberneko/html/SecuritySupport12.java | 138 + src/main/java/org/cyberneko/html/Version.java | 20 + .../cyberneko/html/filters/DefaultFilter.java | 364 ++ .../html/filters/ElementRemover.java | 349 ++ .../org/cyberneko/html/filters/Identity.java | 103 + .../html/filters/NamespaceBinder.java | 643 +++ .../org/cyberneko/html/filters/Purifier.java | 472 ++ .../org/cyberneko/html/filters/Writer.java | 481 +++ .../html/parsers/DOMFragmentParser.java | 577 +++ .../org/cyberneko/html/parsers/DOMParser.java | 118 + .../org/cyberneko/html/parsers/SAXParser.java | 41 + .../html/xercesbridge/XercesBridge.java | 122 + .../html/xercesbridge/XercesBridge_2_2.java | 64 + .../html/xercesbridge/XercesBridge_2_3.java | 45 + .../html/res/ErrorMessages.properties | 42 + .../html/res/ErrorMessages_ja.properties | 36 + .../cyberneko/html/res/ErrorMessages_ja.txt | 36 + .../cyberneko/html/res/HTMLlat1.properties | 101 + .../cyberneko/html/res/HTMLspecial.properties | 37 + .../cyberneko/html/res/HTMLsymbol.properties | 129 + .../cyberneko/html/res/XMLbuiltin.properties | 7 + 35 files changed, 11975 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/cyberneko/html/HTMLAugmentations.java create mode 100644 src/main/java/org/cyberneko/html/HTMLComponent.java create mode 100644 src/main/java/org/cyberneko/html/HTMLConfiguration.java create mode 100644 src/main/java/org/cyberneko/html/HTMLElements.java create mode 100644 src/main/java/org/cyberneko/html/HTMLEntities.java create mode 100644 src/main/java/org/cyberneko/html/HTMLErrorReporter.java create mode 100644 src/main/java/org/cyberneko/html/HTMLEventInfo.java create mode 100644 src/main/java/org/cyberneko/html/HTMLScanner.java create mode 100644 src/main/java/org/cyberneko/html/HTMLTagBalancer.java create mode 100644 src/main/java/org/cyberneko/html/HTMLTagBalancingListener.java create mode 100644 src/main/java/org/cyberneko/html/LostText.java create mode 100644 src/main/java/org/cyberneko/html/ObjectFactory.java create mode 100644 src/main/java/org/cyberneko/html/SecuritySupport.java create mode 100644 src/main/java/org/cyberneko/html/SecuritySupport12.java create mode 100644 src/main/java/org/cyberneko/html/Version.java create mode 100644 src/main/java/org/cyberneko/html/filters/DefaultFilter.java create mode 100644 src/main/java/org/cyberneko/html/filters/ElementRemover.java create mode 100644 src/main/java/org/cyberneko/html/filters/Identity.java create mode 100644 src/main/java/org/cyberneko/html/filters/NamespaceBinder.java create mode 100644 src/main/java/org/cyberneko/html/filters/Purifier.java create mode 100644 src/main/java/org/cyberneko/html/filters/Writer.java create mode 100644 src/main/java/org/cyberneko/html/parsers/DOMFragmentParser.java create mode 100644 src/main/java/org/cyberneko/html/parsers/DOMParser.java create mode 100644 src/main/java/org/cyberneko/html/parsers/SAXParser.java create mode 100644 src/main/java/org/cyberneko/html/xercesbridge/XercesBridge.java create mode 100644 src/main/java/org/cyberneko/html/xercesbridge/XercesBridge_2_2.java create mode 100644 src/main/java/org/cyberneko/html/xercesbridge/XercesBridge_2_3.java create mode 100644 src/main/resources/org/cyberneko/html/res/ErrorMessages.properties create mode 100644 src/main/resources/org/cyberneko/html/res/ErrorMessages_ja.properties create mode 100644 src/main/resources/org/cyberneko/html/res/ErrorMessages_ja.txt create mode 100644 src/main/resources/org/cyberneko/html/res/HTMLlat1.properties create mode 100644 src/main/resources/org/cyberneko/html/res/HTMLspecial.properties create mode 100644 src/main/resources/org/cyberneko/html/res/HTMLsymbol.properties create mode 100644 src/main/resources/org/cyberneko/html/res/XMLbuiltin.properties diff --git a/pom.xml b/pom.xml index 8549e86..2759c3f 100644 --- a/pom.xml +++ b/pom.xml @@ -364,17 +364,7 @@ dom4j 2.2.0 - - net.sourceforge.nekohtml - nekohtml - 1.9.16 - - - xerces - xercesImpl - - - + org.apache.ant ant diff --git a/src/main/java/org/cyberneko/html/HTMLAugmentations.java b/src/main/java/org/cyberneko/html/HTMLAugmentations.java new file mode 100644 index 0000000..61d0d04 --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLAugmentations.java @@ -0,0 +1,134 @@ +/* + * Copyright 2004-2008 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +import java.util.Enumeration; +import java.util.Hashtable; + +import org.apache.xerces.xni.Augmentations; + +/** + * This class is here to overcome the XNI changes to the + * Augmentations interface. In early versions of XNI, the + * augmentations interface contained a clear() method to + * remove all of the items from the augmentations instance. A later + * version of XNI changed this method to removeAllItems(). + * Therefore, this class extends the augmentations interface and + * explicitly implements both of these methods. + *

+ * Note: + * This code is inspired by performance enhancements submitted by + * Marc-AndrÊ Morissette. + * + * @author Andy Clark + */ +public class HTMLAugmentations implements Augmentations { + + // + // Data + // + + /** Augmentation items. */ + protected final Hashtable fItems = new Hashtable(); + + // + // Public methods + // + public HTMLAugmentations() { + // nothing + } + + /** + * Copy constructor + * @param augs the object to copy + */ + HTMLAugmentations(final Augmentations augs) { + for (final Enumeration keys=augs.keys(); keys.hasMoreElements(); ) { + final String key = (String) keys.nextElement(); + Object value = augs.getItem(key); + if (value instanceof HTMLScanner.LocationItem) { + value = new HTMLScanner.LocationItem((HTMLScanner.LocationItem) value); + } + fItems.put(key, value); + } + } + + // since Xerces 2.3.0 + + /** Removes all of the elements in this augmentations object. */ + public void removeAllItems() { + fItems.clear(); + } // removeAllItems() + + // from Xerces 2.0.0 (beta4) until 2.3.0 + + /** Removes all of the elements in this augmentations object. */ + public void clear() { + fItems.clear(); + } // clear() + + // + // Augmentations methods + // + + /** + * Add additional information identified by a key to the Augmentations + * structure. + * + * @param key Identifier, can't be null + * @param item Additional information + * + * @return The previous value of the specified key in the Augmentations + * structure, or null if it did not have one. + */ + public Object putItem(String key, Object item) { + return fItems.put(key, item); + } // putItem(String, Object):Object + + + /** + * Get information identified by a key from the Augmentations structure. + * + * @param key Identifier, can't be null + * + * @return The value to which the key is mapped in the Augmentations + * structure; null if the key is not mapped to any + * value. + */ + public Object getItem(String key) { + return fItems.get(key); + } // getItem(String):Object + + /** + * Remove additional info from the Augmentations structure + * + * @param key Identifier, can't be null + * @return The previous value of the specified key in the Augmentations + * structure, or null if it did not have one. + */ + public Object removeItem(String key) { + return fItems.remove(key); + } // removeItem(String):Object + + /** + * Returns an enumeration of the keys in the Augmentations structure. + */ + public Enumeration keys() { + return fItems.keys(); + } // keys():Enumeration + +} // class HTMLAugmentations diff --git a/src/main/java/org/cyberneko/html/HTMLComponent.java b/src/main/java/org/cyberneko/html/HTMLComponent.java new file mode 100644 index 0000000..aedc1ef --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLComponent.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2008 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +import org.apache.xerces.xni.parser.XMLComponent; + +/** + * This interface extends the XNI XMLComponent interface + * to add methods that allow the preferred default values for features + * and properties to be queried. + * + * @author Andy Clark + * + * @version $Id: HTMLComponent.java,v 1.4 2005/02/14 03:56:54 andyc Exp $ + */ +public interface HTMLComponent + extends XMLComponent { + + // + // HTMLComponent methods + // + + /** + * Returns the default state for a feature, or null if this + * component does not want to report a default value for this + * feature. + */ + public Boolean getFeatureDefault(String featureId); + + /** + * Returns the default state for a property, or null if this + * component does not want to report a default value for this + * property. + */ + public Object getPropertyDefault(String propertyId); + +} // interface HTMLComponent diff --git a/src/main/java/org/cyberneko/html/HTMLConfiguration.java b/src/main/java/org/cyberneko/html/HTMLConfiguration.java new file mode 100644 index 0000000..8fe810e --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLConfiguration.java @@ -0,0 +1,702 @@ +/* + * Copyright 2002-2009 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Vector; + +import org.apache.xerces.util.DefaultErrorHandler; +import org.apache.xerces.util.ParserConfigurationSettings; +import org.apache.xerces.xni.XMLDTDContentModelHandler; +import org.apache.xerces.xni.XMLDTDHandler; +import org.apache.xerces.xni.XMLDocumentHandler; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.parser.XMLConfigurationException; +import org.apache.xerces.xni.parser.XMLDocumentFilter; +import org.apache.xerces.xni.parser.XMLDocumentSource; +import org.apache.xerces.xni.parser.XMLEntityResolver; +import org.apache.xerces.xni.parser.XMLErrorHandler; +import org.apache.xerces.xni.parser.XMLInputSource; +import org.apache.xerces.xni.parser.XMLParseException; +import org.apache.xerces.xni.parser.XMLPullParserConfiguration; +import org.cyberneko.html.filters.NamespaceBinder; +import org.cyberneko.html.xercesbridge.XercesBridge; + +/** + * An XNI-based parser configuration that can be used to parse HTML + * documents. This configuration can be used directly in order to + * parse HTML documents or can be used in conjunction with any XNI + * based tools, such as the Xerces2 implementation. + *

+ * This configuration recognizes the following features: + *

    + *
  • http://cyberneko.org/html/features/augmentations + *
  • http://cyberneko.org/html/features/report-errors + *
  • http://cyberneko.org/html/features/report-errors/simple + *
  • http://cyberneko.org/html/features/balance-tags + *
  • and + *
  • the features supported by the scanner and tag balancer components. + *
+ *

+ * This configuration recognizes the following properties: + *

    + *
  • http://cyberneko.org/html/properties/names/elems + *
  • http://cyberneko.org/html/properties/names/attrs + *
  • http://cyberneko.org/html/properties/filters + *
  • http://cyberneko.org/html/properties/error-reporter + *
  • and + *
  • the properties supported by the scanner and tag balancer. + *
+ *

+ * For complete usage information, refer to the documentation. + * + * @see HTMLScanner + * @see HTMLTagBalancer + * @see HTMLErrorReporter + * + * @author Andy Clark + * + * @version $Id: HTMLConfiguration.java,v 1.9 2005/02/14 03:56:54 andyc Exp $ + */ +public class HTMLConfiguration + extends ParserConfigurationSettings + implements XMLPullParserConfiguration { + + // + // Constants + // + + // features + + /** Namespaces. */ + protected static final String NAMESPACES = "http://xml.org/sax/features/namespaces"; + + /** Include infoset augmentations. */ + protected static final String AUGMENTATIONS = "http://cyberneko.org/html/features/augmentations"; + + /** Report errors. */ + protected static final String REPORT_ERRORS = "http://cyberneko.org/html/features/report-errors"; + + /** Simple report format. */ + protected static final String SIMPLE_ERROR_FORMAT = "http://cyberneko.org/html/features/report-errors/simple"; + + /** Balance tags. */ + protected static final String BALANCE_TAGS = "http://cyberneko.org/html/features/balance-tags"; + + // properties + + /** Modify HTML element names: { "upper", "lower", "default" }. */ + protected static final String NAMES_ELEMS = "http://cyberneko.org/html/properties/names/elems"; + + /** Modify HTML attribute names: { "upper", "lower", "default" }. */ + protected static final String NAMES_ATTRS = "http://cyberneko.org/html/properties/names/attrs"; + + /** Pipeline filters. */ + protected static final String FILTERS = "http://cyberneko.org/html/properties/filters"; + + /** Error reporter. */ + protected static final String ERROR_REPORTER = "http://cyberneko.org/html/properties/error-reporter"; + + // other + + /** Error domain. */ + protected static final String ERROR_DOMAIN = "http://cyberneko.org/html"; + + // private + + /** Document source class array. */ + private static final Class[] DOCSOURCE = { XMLDocumentSource.class }; + + // + // Data + // + + // handlers + + /** Document handler. */ + protected XMLDocumentHandler fDocumentHandler; + + /** DTD handler. */ + protected XMLDTDHandler fDTDHandler; + + /** DTD content model handler. */ + protected XMLDTDContentModelHandler fDTDContentModelHandler; + + /** Error handler. */ + protected XMLErrorHandler fErrorHandler = new DefaultErrorHandler(); + + // other settings + + /** Entity resolver. */ + protected XMLEntityResolver fEntityResolver; + + /** Locale. */ + protected Locale fLocale = Locale.getDefault(); + + // state + + /** + * Stream opened by parser. Therefore, must close stream manually upon + * termination of parsing. + */ + protected boolean fCloseStream; + + // components + + /** Components. */ + protected final Vector fHTMLComponents = new Vector(2); + + // pipeline + + /** Document scanner. */ + protected final HTMLScanner fDocumentScanner = createDocumentScanner(); + + /** HTML tag balancer. */ + protected final HTMLTagBalancer fTagBalancer = new HTMLTagBalancer(); + + /** Namespace binder. */ + protected final NamespaceBinder fNamespaceBinder = new NamespaceBinder(); + + // other components + + /** Error reporter. */ + protected final HTMLErrorReporter fErrorReporter = new ErrorReporter(); + + // HACK: workarounds Xerces 2.0.x problems + + /** Parser version is Xerces 2.0.0. */ + protected static boolean XERCES_2_0_0 = false; + + /** Parser version is Xerces 2.0.1. */ + protected static boolean XERCES_2_0_1 = false; + + /** Parser version is XML4J 4.0.x. */ + protected static boolean XML4J_4_0_x = false; + + // + // Static initializer + // + + static { + try { + String VERSION = "org.apache.xerces.impl.Version"; + Object version = ObjectFactory.createObject(VERSION, VERSION); + java.lang.reflect.Field field = version.getClass().getField("fVersion"); + String versionStr = String.valueOf(field.get(version)); + XERCES_2_0_0 = versionStr.equals("Xerces-J 2.0.0"); + XERCES_2_0_1 = versionStr.equals("Xerces-J 2.0.1"); + XML4J_4_0_x = versionStr.startsWith("XML4J 4.0."); + } + catch (Throwable e) { + // ignore + } + } // () + + // + // Constructors + // + + /** Default constructor. */ + public HTMLConfiguration() { + + // add components + addComponent(fDocumentScanner); + addComponent(fTagBalancer); + addComponent(fNamespaceBinder); + + // + // features + // + + // recognized features + String VALIDATION = "http://xml.org/sax/features/validation"; + String[] recognizedFeatures = { + AUGMENTATIONS, + NAMESPACES, + VALIDATION, + REPORT_ERRORS, + SIMPLE_ERROR_FORMAT, + BALANCE_TAGS, + }; + addRecognizedFeatures(recognizedFeatures); + setFeature(AUGMENTATIONS, false); + setFeature(NAMESPACES, true); + setFeature(VALIDATION, false); + setFeature(REPORT_ERRORS, false); + setFeature(SIMPLE_ERROR_FORMAT, false); + setFeature(BALANCE_TAGS, true); + + // HACK: Xerces 2.0.0 + if (XERCES_2_0_0) { + // NOTE: These features should not be required but it causes a + // problem if they're not there. This will be fixed in + // subsequent releases of Xerces. + recognizedFeatures = new String[] { + "http://apache.org/xml/features/scanner/notify-builtin-refs", + }; + addRecognizedFeatures(recognizedFeatures); + } + + // HACK: Xerces 2.0.1 + if (XERCES_2_0_0 || XERCES_2_0_1 || XML4J_4_0_x) { + // NOTE: These features should not be required but it causes a + // problem if they're not there. This should be fixed in + // subsequent releases of Xerces. + recognizedFeatures = new String[] { + "http://apache.org/xml/features/validation/schema/normalized-value", + "http://apache.org/xml/features/scanner/notify-char-refs", + }; + addRecognizedFeatures(recognizedFeatures); + } + + // + // properties + // + + // recognized properties + String[] recognizedProperties = { + NAMES_ELEMS, + NAMES_ATTRS, + FILTERS, + ERROR_REPORTER, + }; + addRecognizedProperties(recognizedProperties); + setProperty(NAMES_ELEMS, "upper"); + setProperty(NAMES_ATTRS, "lower"); + setProperty(ERROR_REPORTER, fErrorReporter); + + // HACK: Xerces 2.0.0 + if (XERCES_2_0_0) { + // NOTE: This is a hack to get around a problem in the Xerces 2.0.0 + // AbstractSAXParser. If it uses a parser configuration that + // does not have a SymbolTable, then it will remove *all* + // attributes. This will be fixed in subsequent releases of + // Xerces. + String SYMBOL_TABLE = "http://apache.org/xml/properties/internal/symbol-table"; + recognizedProperties = new String[] { + SYMBOL_TABLE, + }; + addRecognizedProperties(recognizedProperties); + Object symbolTable = ObjectFactory.createObject("org.apache.xerces.util.SymbolTable", + "org.apache.xerces.util.SymbolTable"); + setProperty(SYMBOL_TABLE, symbolTable); + } + + } // () + + protected HTMLScanner createDocumentScanner() { + return new HTMLScanner(); + } + + // + // Public methods + // + + /** + * Pushes an input source onto the current entity stack. This + * enables the scanner to transparently scan new content (e.g. + * the output written by an embedded script). At the end of the + * current entity, the scanner returns where it left off at the + * time this entity source was pushed. + *

+ * Hint: + * To use this feature to insert the output of <SCRIPT> + * tags, remember to buffer the entire output of the + * processed instructions before pushing a new input source. + * Otherwise, events may appear out of sequence. + * + * @param inputSource The new input source to start scanning. + * @see #evaluateInputSource(XMLInputSource) + */ + public void pushInputSource(XMLInputSource inputSource) { + fDocumentScanner.pushInputSource(inputSource); + } // pushInputSource(XMLInputSource) + + /** + * EXPERIMENTAL: may change in next release
+ * Immediately evaluates an input source and add the new content (e.g. + * the output written by an embedded script). + * + * @param inputSource The new input source to start scanning. + * @see #pushInputSource(XMLInputSource) + */ + public void evaluateInputSource(XMLInputSource inputSource) { + fDocumentScanner.evaluateInputSource(inputSource); + } // evaluateInputSource(XMLInputSource) + + // XMLParserConfiguration methods + // + + /** Sets a feature. */ + public void setFeature(String featureId, boolean state) + throws XMLConfigurationException { + super.setFeature(featureId, state); + int size = fHTMLComponents.size(); + for (int i = 0; i < size; i++) { + HTMLComponent component = (HTMLComponent)fHTMLComponents.elementAt(i); + component.setFeature(featureId, state); + } + } // setFeature(String,boolean) + + /** Sets a property. */ + public void setProperty(String propertyId, Object value) + throws XMLConfigurationException { + super.setProperty(propertyId, value); + + if (propertyId.equals(FILTERS)) { + XMLDocumentFilter[] filters = (XMLDocumentFilter[])getProperty(FILTERS); + if (filters != null) { + for (int i = 0; i < filters.length; i++) { + XMLDocumentFilter filter = filters[i]; + if (filter instanceof HTMLComponent) { + addComponent((HTMLComponent)filter); + } + } + } + } + + int size = fHTMLComponents.size(); + for (int i = 0; i < size; i++) { + HTMLComponent component = (HTMLComponent)fHTMLComponents.elementAt(i); + component.setProperty(propertyId, value); + } + } // setProperty(String,Object) + + /** Sets the document handler. */ + public void setDocumentHandler(XMLDocumentHandler handler) { + fDocumentHandler = handler; + if (handler instanceof HTMLTagBalancingListener) { + fTagBalancer.setTagBalancingListener((HTMLTagBalancingListener) handler); + } + } // setDocumentHandler(XMLDocumentHandler) + + /** Returns the document handler. */ + public XMLDocumentHandler getDocumentHandler() { + return fDocumentHandler; + } // getDocumentHandler():XMLDocumentHandler + + /** Sets the DTD handler. */ + public void setDTDHandler(XMLDTDHandler handler) { + fDTDHandler = handler; + } // setDTDHandler(XMLDTDHandler) + + /** Returns the DTD handler. */ + public XMLDTDHandler getDTDHandler() { + return fDTDHandler; + } // getDTDHandler():XMLDTDHandler + + /** Sets the DTD content model handler. */ + public void setDTDContentModelHandler(XMLDTDContentModelHandler handler) { + fDTDContentModelHandler = handler; + } // setDTDContentModelHandler(XMLDTDContentModelHandler) + + /** Returns the DTD content model handler. */ + public XMLDTDContentModelHandler getDTDContentModelHandler() { + return fDTDContentModelHandler; + } // getDTDContentModelHandler():XMLDTDContentModelHandler + + /** Sets the error handler. */ + public void setErrorHandler(XMLErrorHandler handler) { + fErrorHandler = handler; + } // setErrorHandler(XMLErrorHandler) + + /** Returns the error handler. */ + public XMLErrorHandler getErrorHandler() { + return fErrorHandler; + } // getErrorHandler():XMLErrorHandler + + /** Sets the entity resolver. */ + public void setEntityResolver(XMLEntityResolver resolver) { + fEntityResolver = resolver; + } // setEntityResolver(XMLEntityResolver) + + /** Returns the entity resolver. */ + public XMLEntityResolver getEntityResolver() { + return fEntityResolver; + } // getEntityResolver():XMLEntityResolver + + /** Sets the locale. */ + public void setLocale(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + fLocale = locale; + } // setLocale(Locale) + + /** Returns the locale. */ + public Locale getLocale() { + return fLocale; + } // getLocale():Locale + + /** Parses a document. */ + public void parse(XMLInputSource source) throws XNIException, IOException { + setInputSource(source); + parse(true); + } // parse(XMLInputSource) + + // + // XMLPullParserConfiguration methods + // + + // parsing + + /** + * Sets the input source for the document to parse. + * + * @param inputSource The document's input source. + * + * @exception XMLConfigurationException Thrown if there is a + * configuration error when initializing the + * parser. + * @exception IOException Thrown on I/O error. + * + * @see #parse(boolean) + */ + public void setInputSource(XMLInputSource inputSource) + throws XMLConfigurationException, IOException { + reset(); + fCloseStream = inputSource.getByteStream() == null && + inputSource.getCharacterStream() == null; + fDocumentScanner.setInputSource(inputSource); + } // setInputSource(XMLInputSource) + + /** + * Parses the document in a pull parsing fashion. + * + * @param complete True if the pull parser should parse the + * remaining document completely. + * + * @return True if there is more document to parse. + * + * @exception XNIException Any XNI exception, possibly wrapping + * another exception. + * @exception IOException An IO exception from the parser, possibly + * from a byte stream or character stream + * supplied by the parser. + * + * @see #setInputSource + */ + public boolean parse(boolean complete) throws XNIException, IOException { + try { + boolean more = fDocumentScanner.scanDocument(complete); + if (!more) { + cleanup(); + } + return more; + } + catch (XNIException e) { + cleanup(); + throw e; + } + catch (IOException e) { + cleanup(); + throw e; + } + } // parse(boolean):boolean + + /** + * If the application decides to terminate parsing before the xml document + * is fully parsed, the application should call this method to free any + * resource allocated during parsing. For example, close all opened streams. + */ + public void cleanup() { + fDocumentScanner.cleanup(fCloseStream); + } // cleanup() + + // + // Protected methods + // + + /** Adds a component. */ + protected void addComponent(HTMLComponent component) { + + // add component to list + fHTMLComponents.addElement(component); + + // add recognized features and set default states + String[] features = component.getRecognizedFeatures(); + addRecognizedFeatures(features); + int featureCount = features != null ? features.length : 0; + for (int i = 0; i < featureCount; i++) { + Boolean state = component.getFeatureDefault(features[i]); + if (state != null) { + setFeature(features[i], state.booleanValue()); + } + } + + // add recognized properties and set default values + String[] properties = component.getRecognizedProperties(); + addRecognizedProperties(properties); + int propertyCount = properties != null ? properties.length : 0; + for (int i = 0; i < propertyCount; i++) { + Object value = component.getPropertyDefault(properties[i]); + if (value != null) { + setProperty(properties[i], value); + } + } + + } // addComponent(HTMLComponent) + + /** Resets the parser configuration. */ + protected void reset() throws XMLConfigurationException { + + // reset components + int size = fHTMLComponents.size(); + for (int i = 0; i < size; i++) { + HTMLComponent component = (HTMLComponent)fHTMLComponents.elementAt(i); + component.reset(this); + } + + // configure pipeline + XMLDocumentSource lastSource = fDocumentScanner; + if (getFeature(NAMESPACES)) { + lastSource.setDocumentHandler(fNamespaceBinder); + fNamespaceBinder.setDocumentSource(fTagBalancer); + lastSource = fNamespaceBinder; + } + if (getFeature(BALANCE_TAGS)) { + lastSource.setDocumentHandler(fTagBalancer); + fTagBalancer.setDocumentSource(fDocumentScanner); + lastSource = fTagBalancer; + } + XMLDocumentFilter[] filters = (XMLDocumentFilter[])getProperty(FILTERS); + if (filters != null) { + for (int i = 0; i < filters.length; i++) { + XMLDocumentFilter filter = filters[i]; + XercesBridge.getInstance().XMLDocumentFilter_setDocumentSource(filter, lastSource); + lastSource.setDocumentHandler(filter); + lastSource = filter; + } + } + lastSource.setDocumentHandler(fDocumentHandler); + + } // reset() + + // + // Interfaces + // + + /** + * Defines an error reporter for reporting HTML errors. There is no such + * thing as a fatal error in parsing HTML. I/O errors are fatal but should + * throw an IOException directly instead of reporting an error. + *

+ * When used in a configuration, the error reporter instance should be + * set as a property with the following property identifier: + *

+     * "http://cyberneko.org/html/internal/error-reporter" in the
+     * 
+ * Components in the configuration can query the error reporter using this + * property identifier. + *

+ * Note: + * All reported errors are within the domain "http://cyberneko.org/html". + * + * @author Andy Clark + */ + protected class ErrorReporter + implements HTMLErrorReporter { + + // + // Data + // + + /** Last locale. */ + protected Locale fLastLocale; + + /** Error messages. */ + protected ResourceBundle fErrorMessages; + + // + // HTMLErrorReporter methods + // + + /** Format message without reporting error. */ + public String formatMessage(String key, Object[] args) { + if (!getFeature(SIMPLE_ERROR_FORMAT)) { + if (!fLocale.equals(fLastLocale)) { + fErrorMessages = null; + fLastLocale = fLocale; + } + if (fErrorMessages == null) { + fErrorMessages = + ResourceBundle.getBundle("org/cyberneko/html/res/ErrorMessages", + fLocale); + } + try { + String value = fErrorMessages.getString(key); + String message = MessageFormat.format(value, args); + return message; + } + catch (MissingResourceException e) { + // ignore and return a simple format + } + } + return formatSimpleMessage(key, args); + } // formatMessage(String,Object[]):String + + /** Reports a warning. */ + public void reportWarning(String key, Object[] args) + throws XMLParseException { + if (fErrorHandler != null) { + fErrorHandler.warning(ERROR_DOMAIN, key, createException(key, args)); + } + } // reportWarning(String,Object[]) + + /** Reports an error. */ + public void reportError(String key, Object[] args) + throws XMLParseException { + if (fErrorHandler != null) { + fErrorHandler.error(ERROR_DOMAIN, key, createException(key, args)); + } + } // reportError(String,Object[]) + + // + // Protected methods + // + + /** Creates parse exception. */ + protected XMLParseException createException(String key, Object[] args) { + String message = formatMessage(key, args); + return new XMLParseException(fDocumentScanner, message); + } // createException(String,Object[]):XMLParseException + + /** Format simple message. */ + protected String formatSimpleMessage(String key, Object[] args) { + StringBuffer str = new StringBuffer(); + str.append(ERROR_DOMAIN); + str.append('#'); + str.append(key); + if (args != null && args.length > 0) { + str.append('\t'); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + str.append('\t'); + } + str.append(String.valueOf(args[i])); + } + } + return str.toString(); + } // formatSimpleMessage(String, + + } // class ErrorReporter + +} // class HTMLConfiguration diff --git a/src/main/java/org/cyberneko/html/HTMLElements.java b/src/main/java/org/cyberneko/html/HTMLElements.java new file mode 100644 index 0000000..3075fb2 --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLElements.java @@ -0,0 +1,795 @@ +/* + * Copyright 2002-2009 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +/** + * Collection of HTML element information. + * + * @author Andy Clark + * @author Ahmed Ashour + * @author Marc Guillemot + * + * @version $Id: HTMLElements.java,v 1.12 2005/02/14 07:16:59 andyc Exp $ + */ +public class HTMLElements { + + // + // Constants + // + + // element codes + + // NOTE: The element codes *must* start with 0 and increment in + // sequence. The parent and closes references depends on + // this assumption. -Ac + + public static final short A = 0; + public static final short ABBR = A+1; + public static final short ACRONYM = ABBR+1; + public static final short ADDRESS = ACRONYM+1; + public static final short APPLET = ADDRESS+1; + public static final short AREA = APPLET+1; + public static final short B = AREA+1; + public static final short BASE = B+1; + public static final short BASEFONT = BASE+1; + public static final short BDO = BASEFONT+1; + public static final short BGSOUND = BDO+1; + public static final short BIG = BGSOUND+1; + public static final short BLINK = BIG+1; + public static final short BLOCKQUOTE = BLINK+1; + public static final short BODY = BLOCKQUOTE+1; + public static final short BR = BODY+1; + public static final short BUTTON = BR+1; + public static final short CAPTION = BUTTON+1; + public static final short CENTER = CAPTION+1; + public static final short CITE = CENTER+1; + public static final short CODE = CITE+1; + public static final short COL = CODE+1; + public static final short COLGROUP = COL+1; + public static final short COMMENT = COLGROUP+1; + public static final short DEL = COMMENT+1; + public static final short DFN = DEL+1; + public static final short DIR = DFN+1; + public static final short DIV = DIR+1; + public static final short DD = DIV+1; + public static final short DL = DD+1; + public static final short DT = DL+1; + public static final short EM = DT+1; + public static final short EMBED = EM+1; + public static final short FIELDSET = EMBED+1; + public static final short FONT = FIELDSET+1; + public static final short FORM = FONT+1; + public static final short FRAME = FORM+1; + public static final short FRAMESET = FRAME+1; + public static final short H1 = FRAMESET+1; + public static final short H2 = H1+1; + public static final short H3 = H2+1; + public static final short H4 = H3+1; + public static final short H5 = H4+1; + public static final short H6 = H5+1; + public static final short HEAD = H6+1; + public static final short HR = HEAD+1; + public static final short HTML = HR+1; + public static final short I = HTML+1; + public static final short IFRAME = I+1; + public static final short ILAYER = IFRAME+1; + public static final short IMG = ILAYER+1; + public static final short INPUT = IMG+1; + public static final short INS = INPUT+1; + public static final short ISINDEX = INS+1; + public static final short KBD = ISINDEX+1; + public static final short KEYGEN = KBD+1; + public static final short LABEL = KEYGEN+1; + public static final short LAYER = LABEL+1; + public static final short LEGEND = LAYER+1; + public static final short LI = LEGEND+1; + public static final short LINK = LI+1; + public static final short LISTING = LINK+1; + public static final short MAP = LISTING+1; + public static final short MARQUEE = MAP+1; + public static final short MENU = MARQUEE+1; + public static final short META = MENU+1; + public static final short MULTICOL = META+1; + public static final short NEXTID = MULTICOL+1; + public static final short NOBR = NEXTID+1; + public static final short NOEMBED = NOBR+1; + public static final short NOFRAMES = NOEMBED+1; + public static final short NOLAYER = NOFRAMES+1; + public static final short NOSCRIPT = NOLAYER+1; + public static final short OBJECT = NOSCRIPT+1; + public static final short OL = OBJECT+1; + public static final short OPTION = OL+1; + public static final short OPTGROUP = OPTION+1; + public static final short P = OPTGROUP+1; + public static final short PARAM = P+1; + public static final short PLAINTEXT = PARAM+1; + public static final short PRE = PLAINTEXT+1; + public static final short Q = PRE+1; + public static final short RB = Q+1; + public static final short RBC = RB+1; + public static final short RP = RBC+1; + public static final short RT = RP+1; + public static final short RTC = RT+1; + public static final short RUBY = RTC+1; + public static final short S = RUBY+1; + public static final short SAMP = S+1; + public static final short SCRIPT = SAMP+1; + public static final short SECTION = SCRIPT+1; + public static final short SELECT = SECTION+1; + public static final short SMALL = SELECT+1; + public static final short SOUND = SMALL+1; + public static final short SPACER = SOUND+1; + public static final short SPAN = SPACER+1; + public static final short STRIKE = SPAN+1; + public static final short STRONG = STRIKE+1; + public static final short STYLE = STRONG+1; + public static final short SUB = STYLE+1; + public static final short SUP = SUB+1; + public static final short TABLE = SUP+1; + public static final short TBODY = TABLE+1; + public static final short TD = TBODY+1; + public static final short TEXTAREA = TD+1; + public static final short TFOOT = TEXTAREA+1; + public static final short TH = TFOOT+1; + public static final short THEAD = TH+1; + public static final short TITLE = THEAD+1; + public static final short TR = TITLE+1; + public static final short TT = TR+1; + public static final short U = TT+1; + public static final short UL = U+1; + public static final short VAR = UL+1; + public static final short WBR = VAR+1; + public static final short XML = WBR+1; + public static final short XMP = XML+1; + public static final short UNKNOWN = XMP+1; + + // information + + /** Element information organized by first letter. */ + protected static final Element[][] ELEMENTS_ARRAY = new Element[26][]; + + /** Element information as a contiguous list. */ + protected static final ElementList ELEMENTS = new ElementList(); + + /** No such element. */ + public static final Element NO_SUCH_ELEMENT = new Element(UNKNOWN, "", Element.CONTAINER, new short[]{BODY,HEAD}/*HTML*/, null); + + // + // Static initializer + // + + /** + * Initializes the element information. + *

+ * Note: + * The getElement method requires that the HTML elements + * are added to the list in alphabetical order. If new elements are + * added, then they must be inserted in alphabetical order. + */ + static { + // + // + // + // + // + // + // + // + + // initialize array of element information + ELEMENTS_ARRAY['A'-'A'] = new Element[] { + // A - - (%inline;)* -(A) + new Element(A, "A", Element.CONTAINER, BODY, new short[] {A}), + // ABBR - - (%inline;)* + new Element(ABBR, "ABBR", Element.INLINE, BODY, null), + // ACRONYM - - (%inline;)* + new Element(ACRONYM, "ACRONYM", Element.INLINE, BODY, null), + // ADDRESS - - (%inline;)* + new Element(ADDRESS, "ADDRESS", Element.BLOCK, BODY, new short[] {P}), + // APPLET + new Element(APPLET, "APPLET", Element.CONTAINER, BODY, null), + // AREA - O EMPTY + new Element(AREA, "AREA", Element.EMPTY, MAP, null), + }; + ELEMENTS_ARRAY['B'-'A'] = new Element[] { + // B - - (%inline;)* + new Element(B, "B", Element.INLINE, BODY, null), + // BASE - O EMPTY + new Element(BASE, "BASE", Element.EMPTY, HEAD, null), + // BASEFONT + new Element(BASEFONT, "BASEFONT", Element.EMPTY, HEAD, null), + // BDO - - (%inline;)* + new Element(BDO, "BDO", Element.INLINE, BODY, null), + // BGSOUND + new Element(BGSOUND, "BGSOUND", Element.EMPTY, HEAD, null), + // BIG - - (%inline;)* + new Element(BIG, "BIG", Element.INLINE, BODY, null), + // BLINK + new Element(BLINK, "BLINK", Element.INLINE, BODY, null), + // BLOCKQUOTE - - (%block;|SCRIPT)+ + new Element(BLOCKQUOTE, "BLOCKQUOTE", Element.BLOCK, BODY, new short[]{P}), + // BODY O O (%block;|SCRIPT)+ +(INS|DEL) + new Element(BODY, "BODY", Element.CONTAINER, HTML, new short[]{HEAD}), + // BR - O EMPTY + new Element(BR, "BR", Element.EMPTY, BODY, null), + // BUTTON - - (%flow;)* -(A|%formctrl;|FORM|FIELDSET) + new Element(BUTTON, "BUTTON", Element.INLINE | Element.BLOCK, BODY, new short[]{BUTTON}), + }; + ELEMENTS_ARRAY['C'-'A'] = new Element[] { + // CAPTION - - (%inline;)* + new Element(CAPTION, "CAPTION", Element.INLINE, TABLE, null), + // CENTER, + new Element(CENTER, "CENTER", Element.CONTAINER, BODY, new short[] {P}), + // CITE - - (%inline;)* + new Element(CITE, "CITE", Element.INLINE, BODY, null), + // CODE - - (%inline;)* + new Element(CODE, "CODE", Element.INLINE, BODY, null), + // COL - O EMPTY + new Element(COL, "COL", Element.EMPTY, TABLE, null), + // COLGROUP - O (COL)* + new Element(COLGROUP, "COLGROUP", Element.CONTAINER, TABLE, new short[]{COL,COLGROUP}), + // COMMENT + new Element(COMMENT, "COMMENT", Element.SPECIAL, HTML, null), + }; + ELEMENTS_ARRAY['D'-'A'] = new Element[] { + // DEL - - (%flow;)* + new Element(DEL, "DEL", Element.INLINE, BODY, null), + // DFN - - (%inline;)* + new Element(DFN, "DFN", Element.INLINE, BODY, null), + // DIR + new Element(DIR, "DIR", Element.CONTAINER, BODY, new short[] {P}), + // DIV - - (%flow;)* + new Element(DIV, "DIV", Element.CONTAINER, BODY, new short[]{P}), + // DD - O (%flow;)* + // codavaj: reverted to pre-1.9.18 definition (parent DL, not a block of BODY); + // javadoc 6 pages rely on stray

/
being re-parented into a synthesized
+ new Element(DD, "DD", 0, DL, new short[]{DT,DD}), + // DL - - (DT|DD)+ + // codavaj: reverted to pre-1.9.18 definition (does not close P), see DD above + new Element(DL, "DL", Element.BLOCK, BODY, null), + // DT - O (%inline;)* + // codavaj: reverted to pre-1.9.18 definition, see DD above + new Element(DT, "DT", 0, DL, new short[]{DT,DD}), + }; + ELEMENTS_ARRAY['E'-'A'] = new Element[] { + // EM - - (%inline;)* + new Element(EM, "EM", Element.INLINE, BODY, null), + // EMBED + new Element(EMBED, "EMBED", Element.EMPTY, BODY, null), + }; + ELEMENTS_ARRAY['F'-'A'] = new Element[] { + // FIELDSET - - (#PCDATA,LEGEND,(%flow;)*) + new Element(FIELDSET, "FIELDSET", Element.CONTAINER, BODY, new short[] {P}), + // FONT + new Element(FONT, "FONT", Element.CONTAINER, BODY, null), + // FORM - - (%block;|SCRIPT)+ -(FORM) + new Element(FORM, "FORM", Element.CONTAINER, new short[]{BODY,TD,DIV}, new short[]{BUTTON,P}), + // FRAME - O EMPTY + new Element(FRAME, "FRAME", Element.EMPTY, FRAMESET, null), + // FRAMESET - - ((FRAMESET|FRAME)+ & NOFRAMES?) + new Element(FRAMESET, "FRAMESET", Element.CONTAINER, HTML, null), + }; + ELEMENTS_ARRAY['H'-'A'] = new Element[] { + // (H1|H2|H3|H4|H5|H6) - - (%inline;)* + new Element(H1, "H1", Element.BLOCK, new short[]{BODY,A}, new short[]{H1,H2,H3,H4,H5,H6,P}), + new Element(H2, "H2", Element.BLOCK, new short[]{BODY,A}, new short[]{H1,H2,H3,H4,H5,H6,P}), + new Element(H3, "H3", Element.BLOCK, new short[]{BODY,A}, new short[]{H1,H2,H3,H4,H5,H6,P}), + new Element(H4, "H4", Element.BLOCK, new short[]{BODY,A}, new short[]{H1,H2,H3,H4,H5,H6,P}), + new Element(H5, "H5", Element.BLOCK, new short[]{BODY,A}, new short[]{H1,H2,H3,H4,H5,H6,P}), + new Element(H6, "H6", Element.BLOCK, new short[]{BODY,A}, new short[]{H1,H2,H3,H4,H5,H6,P}), + // HEAD O O (%head.content;) +(%head.misc;) + new Element(HEAD, "HEAD", 0, HTML, null), + // HR - O EMPTY + new Element(HR, "HR", Element.EMPTY, BODY, new short[]{P}), + // HTML O O (%html.content;) + new Element(HTML, "HTML", 0, null, null), + }; + ELEMENTS_ARRAY['I'-'A'] = new Element[] { + // I - - (%inline;)* + new Element(I, "I", Element.INLINE, BODY, null), + // IFRAME + new Element(IFRAME, "IFRAME", Element.BLOCK, BODY, null), + // ILAYER + new Element(ILAYER, "ILAYER", Element.BLOCK, BODY, null), + // IMG - O EMPTY + new Element(IMG, "IMG", Element.EMPTY, BODY, null), + // INPUT - O EMPTY + new Element(INPUT, "INPUT", Element.EMPTY, BODY, null), + // INS - - (%flow;)* + new Element(INS, "INS", Element.INLINE, BODY, null), + // ISINDEX + new Element(ISINDEX, "ISINDEX", Element.INLINE, HEAD, null), + }; + ELEMENTS_ARRAY['K'-'A'] = new Element[] { + // KBD - - (%inline;)* + new Element(KBD, "KBD", Element.INLINE, BODY, null), + // KEYGEN + new Element(KEYGEN, "KEYGEN", Element.EMPTY, BODY, null), + }; + ELEMENTS_ARRAY['L'-'A'] = new Element[] { + // LABEL - - (%inline;)* -(LABEL) + new Element(LABEL, "LABEL", Element.INLINE, BODY, null), + // LAYER + new Element(LAYER, "LAYER", Element.BLOCK, BODY, null), + // LEGEND - - (%inline;)* + new Element(LEGEND, "LEGEND", Element.INLINE, FIELDSET, null), + // LI - O (%flow;)* + new Element(LI, "LI", Element.CONTAINER, new short[]{BODY,UL,OL}, new short[]{LI,P}), + // LINK - O EMPTY + new Element(LINK, "LINK", Element.EMPTY, HEAD, null), + // LISTING + new Element(LISTING, "LISTING", Element.BLOCK, BODY, new short[] {P}), + }; + ELEMENTS_ARRAY['M'-'A'] = new Element[] { + // MAP - - ((%block;) | AREA)+ + new Element(MAP, "MAP", Element.INLINE, BODY, null), + // MARQUEE + new Element(MARQUEE, "MARQUEE", Element.CONTAINER, BODY, null), + // MENU + new Element(MENU, "MENU", Element.CONTAINER, BODY, new short[] {P}), + // META - O EMPTY + new Element(META, "META", Element.EMPTY, HEAD, new short[]{STYLE,TITLE}), + // MULTICOL + new Element(MULTICOL, "MULTICOL", Element.CONTAINER, BODY, null), + }; + ELEMENTS_ARRAY['N'-'A'] = new Element[] { + // NEXTID + new Element(NEXTID, "NEXTID", Element.EMPTY, BODY, null), + // NOBR + new Element(NOBR, "NOBR", Element.INLINE, BODY, new short[]{NOBR}), + // NOEMBED + new Element(NOEMBED, "NOEMBED", Element.CONTAINER, BODY, null), + // NOFRAMES - - (BODY) -(NOFRAMES) + new Element(NOFRAMES, "NOFRAMES", Element.CONTAINER, null, null), + // NOLAYER + new Element(NOLAYER, "NOLAYER", Element.CONTAINER, BODY, null), + // NOSCRIPT - - (%block;)+ + new Element(NOSCRIPT, "NOSCRIPT", Element.CONTAINER, new short[]{BODY}, null), + }; + ELEMENTS_ARRAY['O'-'A'] = new Element[] { + // OBJECT - - (PARAM | %flow;)* + new Element(OBJECT, "OBJECT", Element.CONTAINER, BODY, null), + // OL - - (LI)+ + new Element(OL, "OL", Element.BLOCK, BODY, new short[] {P}), + // OPTGROUP - - (OPTION)+ + new Element(OPTGROUP, "OPTGROUP", 0, SELECT, new short[]{OPTION}), + // OPTION - O (#PCDATA) + new Element(OPTION, "OPTION", 0, SELECT, new short[]{OPTION}), + }; + ELEMENTS_ARRAY['P'-'A'] = new Element[] { + // P - O (%inline;)* + new Element(P, "P", Element.CONTAINER, BODY, new short[]{P}), + // PARAM - O EMPTY + new Element(PARAM, "PARAM", Element.EMPTY, new short[]{OBJECT,APPLET}, null), + // PLAINTEXT + new Element(PLAINTEXT, "PLAINTEXT", Element.SPECIAL, BODY, null), + // PRE - - (%inline;)* -(%pre.exclusion;) + // codavaj: reverted to pre-1.9.18 definition (does not close P); javadoc 6 + // signature
 blocks live inside 

and must stay there + new Element(PRE, "PRE", 0, BODY, null), + }; + ELEMENTS_ARRAY['Q'-'A'] = new Element[] { + // Q - - (%inline;)* + new Element(Q, "Q", Element.INLINE, BODY, null), + }; + ELEMENTS_ARRAY['R'-'A'] = new Element[] { + // RB + new Element(RB, "RB", Element.INLINE, RUBY, new short[]{RB}), + // RBC + new Element(RBC, "RBC", 0, RUBY, null), + // RP + new Element(RP, "RP", Element.INLINE, RUBY, new short[]{RB}), + // RT + new Element(RT, "RT", Element.INLINE, RUBY, new short[]{RB,RP}), + // RTC + new Element(RTC, "RTC", 0, RUBY, new short[]{RBC}), + // RUBY + new Element(RUBY, "RUBY", Element.CONTAINER, BODY, new short[]{RUBY}), + }; + ELEMENTS_ARRAY['S'-'A'] = new Element[] { + // S + new Element(S, "S", Element.INLINE, BODY, null), + // SAMP - - (%inline;)* + new Element(SAMP, "SAMP", Element.INLINE, BODY, null), + // SCRIPT - - %Script; + new Element(SCRIPT, "SCRIPT", Element.SPECIAL, new short[]{HEAD,BODY}, null), + + new Element(SECTION, "SECTION", Element.CONTAINER, BODY, new short[]{SELECT}), + // SELECT - - (OPTGROUP|OPTION)+ + new Element(SELECT, "SELECT", Element.CONTAINER, BODY, new short[]{SELECT}), + // SMALL - - (%inline;)* + new Element(SMALL, "SMALL", Element.INLINE, BODY, null), + // SOUND + new Element(SOUND, "SOUND", Element.EMPTY, HEAD, null), + // SPACER + new Element(SPACER, "SPACER", Element.EMPTY, BODY, null), + // SPAN - - (%inline;)* + new Element(SPAN, "SPAN", Element.CONTAINER, BODY, null), + // STRIKE + new Element(STRIKE, "STRIKE", Element.INLINE, BODY, null), + // STRONG - - (%inline;)* + new Element(STRONG, "STRONG", Element.INLINE, BODY, null), + // STYLE - - %StyleSheet; + new Element(STYLE, "STYLE", Element.SPECIAL, new short[]{HEAD,BODY}, new short[]{STYLE,TITLE,META}), + // SUB - - (%inline;)* + new Element(SUB, "SUB", Element.INLINE, BODY, null), + // SUP - - (%inline;)* + new Element(SUP, "SUP", Element.INLINE, BODY, null), + }; + ELEMENTS_ARRAY['T'-'A'] = new Element[] { + // TABLE - - (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+) + new Element(TABLE, "TABLE", Element.BLOCK|Element.CONTAINER, BODY, null), + // TBODY O O (TR)+ + new Element(TBODY, "TBODY", 0, TABLE, new short[]{THEAD,TBODY,TFOOT,TD,TH,TR,COLGROUP}), + // TD - O (%flow;)* + new Element(TD, "TD", Element.CONTAINER, TR, TABLE, new short[]{TD,TH}), + // TEXTAREA - - (#PCDATA) + new Element(TEXTAREA, "TEXTAREA", Element.SPECIAL, BODY, null), + // TFOOT - O (TR)+ + new Element(TFOOT, "TFOOT", 0, TABLE, new short[]{THEAD,TBODY,TFOOT,TD,TH,TR}), + // TH - O (%flow;)* + new Element(TH, "TH", Element.CONTAINER, TR, TABLE, new short[]{TD,TH}), + // THEAD - O (TR)+ + new Element(THEAD, "THEAD", 0, TABLE, new short[]{THEAD,TBODY,TFOOT,TD,TH,TR,COLGROUP}), + // TITLE - - (#PCDATA) -(%head.misc;) + new Element(TITLE, "TITLE", Element.SPECIAL, new short[]{HEAD,BODY}, null), + // TR - O (TH|TD)+ + new Element(TR, "TR", Element.BLOCK, new short[]{TBODY, THEAD, TFOOT}, TABLE, new short[]{TD,TH,TR,COLGROUP,DIV}), + // TT - - (%inline;)* + new Element(TT, "TT", Element.INLINE, BODY, null), + }; + ELEMENTS_ARRAY['U'-'A'] = new Element[] { + // U, + new Element(U, "U", Element.INLINE, BODY, null), + // UL - - (LI)+ + new Element(UL, "UL", Element.CONTAINER, BODY, new short[] {P}), + }; + ELEMENTS_ARRAY['V'-'A'] = new Element[] { + // VAR - - (%inline;)* + new Element(VAR, "VAR", Element.INLINE, BODY, null), + }; + ELEMENTS_ARRAY['W'-'A'] = new Element[] { + // WBR + new Element(WBR, "WBR", Element.EMPTY, BODY, null), + }; + ELEMENTS_ARRAY['X'-'A'] = new Element[] { + // XML + new Element(XML, "XML", 0, BODY, null), + // XMP + new Element(XMP, "XMP", Element.SPECIAL, BODY, new short[] {P}), + }; + + // keep contiguous list of elements for lookups by code + for (int i = 0; i < ELEMENTS_ARRAY.length; i++) { + Element[] elements = ELEMENTS_ARRAY[i]; + if (elements != null) { + for (int j = 0; j < elements.length; j++) { + Element element = elements[j]; + ELEMENTS.addElement(element); + } + } + } + ELEMENTS.addElement(NO_SUCH_ELEMENT); + + // initialize cross references to parent elements + for (int i = 0; i < ELEMENTS.size; i++) { + Element element = ELEMENTS.data[i]; + if (element.parentCodes != null) { + element.parent = new Element[element.parentCodes.length]; + for (int j = 0; j < element.parentCodes.length; j++) { + element.parent[j] = ELEMENTS.data[element.parentCodes[j]]; + } + element.parentCodes = null; + } + } + + } // () + + // + // Public static methods + // + + /** + * Returns the element information for the specified element code. + * + * @param code The element code. + */ + public static final Element getElement(final short code) { + return ELEMENTS.data[code]; + } // getElement(short):Element + + /** + * Returns the element information for the specified element name. + * + * @param ename The element name. + */ + public static final Element getElement(final String ename) { + Element element = getElement(ename, NO_SUCH_ELEMENT); + if (element == NO_SUCH_ELEMENT) { + element = new Element(UNKNOWN, ename.toUpperCase(), Element.CONTAINER, new short[]{BODY,HEAD}/*HTML*/, null); + element.parent = NO_SUCH_ELEMENT.parent; + element.parentCodes = NO_SUCH_ELEMENT.parentCodes; + } + return element; + } // getElement(String):Element + + /** + * Returns the element information for the specified element name. + * + * @param ename The element name. + * @param element The default element to return if not found. + */ + public static final Element getElement(final String ename, final Element element) { + + if (ename.length() > 0) { + int c = ename.charAt(0); + if (c >= 'a' && c <= 'z') { + c = 'A' + c - 'a'; + } + if (c >= 'A' && c <= 'Z') { + Element[] elements = ELEMENTS_ARRAY[c - 'A']; + if (elements != null) { + for (int i = 0; i < elements.length; i++) { + Element elem = elements[i]; + if (elem.name.equalsIgnoreCase(ename)) { + return elem; + } + } + } + } + } + return element; + + } // getElement(String):Element + + // + // Classes + // + + /** + * Element information. + * + * @author Andy Clark + */ + public static class Element { + + // + // Constants + // + + /** Inline element. */ + public static final int INLINE = 0x01; + + /** Block element. */ + public static final int BLOCK = 0x02; + + /** Empty element. */ + public static final int EMPTY = 0x04; + + /** Container element. */ + public static final int CONTAINER = 0x08; + + /** Special element. */ + public static final int SPECIAL = 0x10; + + // + // Data + // + + /** The element code. */ + public short code; + + /** The element name. */ + public String name; + + /** Informational flags. */ + public int flags; + + /** Parent elements. */ + public short[] parentCodes; + + /** Parent elements. */ + public Element[] parent; + + /** The bounding element code. */ + public short bounds; + + /** List of elements this element can close. */ + public short[] closes; + + // + // Constructors + // + + /** + * Constructs an element object. + * + * @param code The element code. + * @param name The element name. + * @param flags Informational flags + * @param parent Natural closing parent name. + * @param closes List of elements this element can close. + */ + public Element(final short code, final String name, final int flags, + final short parent, final short[] closes) { + this(code, name, flags, new short[]{parent}, (short)-1, closes); + } // (short,String,int,short,short[]); + + /** + * Constructs an element object. + * + * @param code The element code. + * @param name The element name. + * @param flags Informational flags + * @param parent Natural closing parent name. + * @param closes List of elements this element can close. + */ + public Element(final short code, final String name, final int flags, + final short parent, final short bounds, final short[] closes) { + this(code, name, flags, new short[]{parent}, bounds, closes); + } // (short,String,int,short,short,short[]) + + /** + * Constructs an element object. + * + * @param code The element code. + * @param name The element name. + * @param flags Informational flags + * @param parents Natural closing parent names. + * @param closes List of elements this element can close. + */ + public Element(final short code, final String name, final int flags, + final short[] parents, final short[] closes) { + this(code, name, flags, parents, (short)-1, closes); + } // (short,String,int,short[],short[]) + + /** + * Constructs an element object. + * + * @param code The element code. + * @param name The element name. + * @param flags Informational flags + * @param parents Natural closing parent names. + * @param closes List of elements this element can close. + */ + public Element(final short code, final String name, final int flags, + final short[] parents, final short bounds, final short[] closes) { + this.code = code; + this.name = name; + this.flags = flags; + this.parentCodes = parents; + this.parent = null; + this.bounds = bounds; + this.closes = closes; + } // (short,String,int,short[],short,short[]) + + // + // Public methods + // + + /** Returns true if this element is an inline element. */ + public final boolean isInline() { + return (flags & INLINE) != 0; + } // isInline():boolean + + /** Returns true if this element is a block element. */ + public final boolean isBlock() { + return (flags & BLOCK) != 0; + } // isBlock():boolean + + /** Returns true if this element is an empty element. */ + public final boolean isEmpty() { + return (flags & EMPTY) != 0; + } // isEmpty():boolean + + /** Returns true if this element is a container element. */ + public final boolean isContainer() { + return (flags & CONTAINER) != 0; + } // isContainer():boolean + + /** + * Returns true if this element is special -- if its content + * should be parsed ignoring markup. + */ + public final boolean isSpecial() { + return (flags & SPECIAL) != 0; + } // isSpecial():boolean + + /** + * Returns true if this element can close the specified Element. + * + * @param tag The element. + */ + public boolean closes(final short tag) { + if (closes != null) { + for (int i = 0; i < closes.length; i++) { + if (closes[i] == tag) { + return true; + } + } + } + return false; + + } // closes(short):boolean + + // + // Object methods + // + + /** Returns a hash code for this object. */ + public int hashCode() { + return name.hashCode(); + } // hashCode():int + + /** Returns true if the objects are equal. */ + public boolean equals(final Object o) { + return name.equals(o); + } // equals(Object):boolean + + /** + * Provides a simple representation to make debugging easier + */ + public String toString() { + return super.toString() + "(name=" + name + ")"; + } + + /** + * Indicates if the provided element is an accepted parent of current element + * @param element the element to test for "paternity" + * @return true if element belongs to the {@link #parent} + */ + public boolean isParent(final Element element) { + if (parent == null) + return false; + for (int i=0; iIOException directly instead of reporting an error. + *

+ * When used in a configuration, the error reporter instance should be + * set as a property with the following property identifier: + *

+ * "http://cyberneko.org/html/internal/error-reporter" in the
+ * 
+ * Components in the configuration can query the error reporter using this + * property identifier. + *

+ * Note: + * All reported errors are within the domain "http://cyberneko.org/html". + * + * @author Andy Clark + * + * @version $Id: HTMLErrorReporter.java,v 1.4 2005/02/14 03:56:54 andyc Exp $ + */ +public interface HTMLErrorReporter { + + // + // HTMLErrorReporter methods + // + + /** Format message without reporting error. */ + public String formatMessage(String key, Object[] args); + + /** Reports a warning. */ + public void reportWarning(String key, Object[] args) throws XMLParseException; + + /** Reports an error. */ + public void reportError(String key, Object[] args) throws XMLParseException; + +} // interface HTMLErrorReporter diff --git a/src/main/java/org/cyberneko/html/HTMLEventInfo.java b/src/main/java/org/cyberneko/html/HTMLEventInfo.java new file mode 100644 index 0000000..8ce22ad --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLEventInfo.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2009 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +/** + * This interface is used to pass augmentated information to the + * application through the XNI pipeline. + * + * @author Andy Clark + * + * @version $Id: HTMLEventInfo.java,v 1.4 2005/02/14 03:56:54 andyc Exp $ + */ +public interface HTMLEventInfo { + + // + // HTMLEventInfo methods + // + + // location information + + /** Returns the line number of the beginning of this event.*/ + public int getBeginLineNumber(); + + /** Returns the column number of the beginning of this event.*/ + public int getBeginColumnNumber(); + + /** Returns the character offset of the beginning of this event.*/ + public int getBeginCharacterOffset(); + + /** Returns the line number of the end of this event.*/ + public int getEndLineNumber(); + + /** Returns the column number of the end of this event.*/ + public int getEndColumnNumber(); + + /** Returns the character offset of the end of this event.*/ + public int getEndCharacterOffset(); + + // other information + + /** Returns true if this corresponding event was synthesized. */ + public boolean isSynthesized(); + + /** + * Synthesized infoset item. + * + * @author Andy Clark + */ + public static class SynthesizedItem + implements HTMLEventInfo { + + // + // HTMLEventInfo methods + // + + // location information + + /** Returns the line number of the beginning of this event.*/ + public int getBeginLineNumber() { + return -1; + } // getBeginLineNumber():int + + /** Returns the column number of the beginning of this event.*/ + public int getBeginColumnNumber() { + return -1; + } // getBeginColumnNumber():int + + /** Returns the character offset of the beginning of this event.*/ + public int getBeginCharacterOffset() { + return -1; + } // getBeginCharacterOffset():int + + /** Returns the line number of the end of this event.*/ + public int getEndLineNumber() { + return -1; + } // getEndLineNumber():int + + /** Returns the column number of the end of this event.*/ + public int getEndColumnNumber() { + return -1; + } // getEndColumnNumber():int + + /** Returns the character offset of the end of this event.*/ + public int getEndCharacterOffset() { + return -1; + } // getEndCharacterOffset():int + + // other information + + /** Returns true if this corresponding event was synthesized. */ + public boolean isSynthesized() { + return true; + } // isSynthesized():boolean + + // + // Object methods + // + + /** Returns a string representation of this object. */ + public String toString() { + return "synthesized"; + } // toString():String + + } // class SynthesizedItem + +} // interface HTMLEventInfo diff --git a/src/main/java/org/cyberneko/html/HTMLScanner.java b/src/main/java/org/cyberneko/html/HTMLScanner.java new file mode 100644 index 0000000..6064c0b --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLScanner.java @@ -0,0 +1,3841 @@ +/* + * Copyright 2002-2009 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.util.BitSet; +import java.util.Locale; +import java.util.Stack; + +import org.apache.xerces.util.EncodingMap; +import org.apache.xerces.util.NamespaceSupport; +import org.apache.xerces.util.URI; +import org.apache.xerces.util.XMLAttributesImpl; +import org.apache.xerces.util.XMLResourceIdentifierImpl; +import org.apache.xerces.util.XMLStringBuffer; +import org.apache.xerces.xni.Augmentations; +import org.apache.xerces.xni.NamespaceContext; +import org.apache.xerces.xni.QName; +import org.apache.xerces.xni.XMLAttributes; +import org.apache.xerces.xni.XMLDocumentHandler; +import org.apache.xerces.xni.XMLLocator; +import org.apache.xerces.xni.XMLResourceIdentifier; +import org.apache.xerces.xni.XMLString; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.parser.XMLComponentManager; +import org.apache.xerces.xni.parser.XMLConfigurationException; +import org.apache.xerces.xni.parser.XMLDocumentScanner; +import org.apache.xerces.xni.parser.XMLInputSource; +import org.cyberneko.html.xercesbridge.XercesBridge; + +/** + * A simple HTML scanner. This scanner makes no attempt to balance tags + * or fix other problems in the source document — it just scans what + * it can and generates XNI document "events", ignoring errors of all + * kinds. + *

+ * This component recognizes the following features: + *

    + *
  • http://cyberneko.org/html/features/augmentations + *
  • http://cyberneko.org/html/features/report-errors + *
  • http://apache.org/xml/features/scanner/notify-char-refs + *
  • http://apache.org/xml/features/scanner/notify-builtin-refs + *
  • http://cyberneko.org/html/features/scanner/notify-builtin-refs + *
  • http://cyberneko.org/html/features/scanner/fix-mswindows-refs + *
  • http://cyberneko.org/html/features/scanner/script/strip-cdata-delims + *
  • http://cyberneko.org/html/features/scanner/script/strip-comment-delims + *
  • http://cyberneko.org/html/features/scanner/style/strip-cdata-delims + *
  • http://cyberneko.org/html/features/scanner/style/strip-comment-delims + *
  • http://cyberneko.org/html/features/scanner/ignore-specified-charset + *
  • http://cyberneko.org/html/features/scanner/cdata-sections + *
  • http://cyberneko.org/html/features/override-doctype + *
  • http://cyberneko.org/html/features/insert-doctype + *
  • http://cyberneko.org/html/features/parse-noscript-content + *
  • http://cyberneko.org/html/features/scanner/allow-selfclosing-iframe + *
  • http://cyberneko.org/html/features/scanner/allow-selfclosing-tags + *
+ *

+ * This component recognizes the following properties: + *

    + *
  • http://cyberneko.org/html/properties/names/elems + *
  • http://cyberneko.org/html/properties/names/attrs + *
  • http://cyberneko.org/html/properties/default-encoding + *
  • http://cyberneko.org/html/properties/error-reporter + *
  • http://cyberneko.org/html/properties/doctype/pubid + *
  • http://cyberneko.org/html/properties/doctype/sysid + *
+ * + * @see HTMLElements + * @see HTMLEntities + * + * @author Andy Clark + * @author Marc Guillemot + * @author Ahmed Ashour + * + * @version $Id: HTMLScanner.java,v 1.19 2005/06/14 05:52:37 andyc Exp $ + */ +public class HTMLScanner + implements XMLDocumentScanner, XMLLocator, HTMLComponent { + + // + // Constants + // + + // doctype info: HTML 4.01 strict + + /** HTML 4.01 strict public identifier ("-//W3C//DTD HTML 4.01//EN"). */ + public static final String HTML_4_01_STRICT_PUBID = "-//W3C//DTD HTML 4.01//EN"; + + /** HTML 4.01 strict system identifier ("http://www.w3.org/TR/html4/strict.dtd"). */ + public static final String HTML_4_01_STRICT_SYSID = "http://www.w3.org/TR/html4/strict.dtd"; + + // doctype info: HTML 4.01 loose + + /** HTML 4.01 transitional public identifier ("-//W3C//DTD HTML 4.01 Transitional//EN"). */ + public static final String HTML_4_01_TRANSITIONAL_PUBID = "-//W3C//DTD HTML 4.01 Transitional//EN"; + + /** HTML 4.01 transitional system identifier ("http://www.w3.org/TR/html4/loose.dtd"). */ + public static final String HTML_4_01_TRANSITIONAL_SYSID = "http://www.w3.org/TR/html4/loose.dtd"; + + // doctype info: HTML 4.01 frameset + + /** HTML 4.01 frameset public identifier ("-//W3C//DTD HTML 4.01 Frameset//EN"). */ + public static final String HTML_4_01_FRAMESET_PUBID = "-//W3C//DTD HTML 4.01 Frameset//EN"; + + /** HTML 4.01 frameset system identifier ("http://www.w3.org/TR/html4/frameset.dtd"). */ + public static final String HTML_4_01_FRAMESET_SYSID = "http://www.w3.org/TR/html4/frameset.dtd"; + + // features + + /** Include infoset augmentations. */ + protected static final String AUGMENTATIONS = "http://cyberneko.org/html/features/augmentations"; + + /** Report errors. */ + protected static final String REPORT_ERRORS = "http://cyberneko.org/html/features/report-errors"; + + /** Notify character entity references (e.g. &#32;, &#x20;, etc). */ + public static final String NOTIFY_CHAR_REFS = "http://apache.org/xml/features/scanner/notify-char-refs"; + + /** + * Notify handler of built-in entity references (e.g. &amp;, + * &lt;, etc). + *

+ * Note: + * This only applies to the five pre-defined XML general entities. + * Specifically, "amp", "lt", "gt", "quot", and "apos". This is done + * for compatibility with the Xerces feature. + *

+ * To be notified of the built-in entity references in HTML, set the + * http://cyberneko.org/html/features/scanner/notify-builtin-refs + * feature to true. + */ + public static final String NOTIFY_XML_BUILTIN_REFS = "http://apache.org/xml/features/scanner/notify-builtin-refs"; + + /** + * Notify handler of built-in entity references (e.g. &nobr;, + * &copy;, etc). + *

+ * Note: + * This includes the five pre-defined XML general entities. + */ + public static final String NOTIFY_HTML_BUILTIN_REFS = "http://cyberneko.org/html/features/scanner/notify-builtin-refs"; + + /** Fix Microsoft Windows® character entity references. */ + public static final String FIX_MSWINDOWS_REFS = "http://cyberneko.org/html/features/scanner/fix-mswindows-refs"; + + /** + * Strip HTML comment delimiters ("<!−−" and + * "−−>") from SCRIPT tag contents. + */ + public static final String SCRIPT_STRIP_COMMENT_DELIMS = "http://cyberneko.org/html/features/scanner/script/strip-comment-delims"; + + /** + * Strip XHTML CDATA delimiters ("<![CDATA[" and "]]>") from + * SCRIPT tag contents. + */ + public static final String SCRIPT_STRIP_CDATA_DELIMS = "http://cyberneko.org/html/features/scanner/script/strip-cdata-delims"; + + /** + * Strip HTML comment delimiters ("<!−−" and + * "−−>") from STYLE tag contents. + */ + public static final String STYLE_STRIP_COMMENT_DELIMS = "http://cyberneko.org/html/features/scanner/style/strip-comment-delims"; + + /** + * Strip XHTML CDATA delimiters ("<![CDATA[" and "]]>") from + * STYLE tag contents. + */ + public static final String STYLE_STRIP_CDATA_DELIMS = "http://cyberneko.org/html/features/scanner/style/strip-cdata-delims"; + + /** + * Ignore specified charset found in the <meta equiv='Content-Type' + * content='text/html;charset=…'> tag or in the <?xml … encoding='…'> processing instruction + */ + public static final String IGNORE_SPECIFIED_CHARSET = "http://cyberneko.org/html/features/scanner/ignore-specified-charset"; + + /** Scan CDATA sections. */ + public static final String CDATA_SECTIONS = "http://cyberneko.org/html/features/scanner/cdata-sections"; + + /** Override doctype declaration public and system identifiers. */ + public static final String OVERRIDE_DOCTYPE = "http://cyberneko.org/html/features/override-doctype"; + + /** Insert document type declaration. */ + public static final String INSERT_DOCTYPE = "http://cyberneko.org/html/features/insert-doctype"; + + /** Parse <noscript>...</noscript> content */ + public static final String PARSE_NOSCRIPT_CONTENT = "http://cyberneko.org/html/features/parse-noscript-content"; + + /** Allows self closing <iframe/> tag */ + public static final String ALLOW_SELFCLOSING_IFRAME = "http://cyberneko.org/html/features/scanner/allow-selfclosing-iframe"; + + /** Allows self closing tags e.g. <div/> (XHTML) */ + public static final String ALLOW_SELFCLOSING_TAGS = "http://cyberneko.org/html/features/scanner/allow-selfclosing-tags"; + + /** Normalize attribute values. */ + protected static final String NORMALIZE_ATTRIBUTES = "http://cyberneko.org/html/features/scanner/normalize-attrs"; + + /** Recognized features. */ + private static final String[] RECOGNIZED_FEATURES = { + AUGMENTATIONS, + REPORT_ERRORS, + NOTIFY_CHAR_REFS, + NOTIFY_XML_BUILTIN_REFS, + NOTIFY_HTML_BUILTIN_REFS, + FIX_MSWINDOWS_REFS, + SCRIPT_STRIP_CDATA_DELIMS, + SCRIPT_STRIP_COMMENT_DELIMS, + STYLE_STRIP_CDATA_DELIMS, + STYLE_STRIP_COMMENT_DELIMS, + IGNORE_SPECIFIED_CHARSET, + CDATA_SECTIONS, + OVERRIDE_DOCTYPE, + INSERT_DOCTYPE, + NORMALIZE_ATTRIBUTES, + PARSE_NOSCRIPT_CONTENT, + ALLOW_SELFCLOSING_IFRAME, + ALLOW_SELFCLOSING_TAGS, + }; + + /** Recognized features defaults. */ + private static final Boolean[] RECOGNIZED_FEATURES_DEFAULTS = { + null, + null, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.FALSE, + Boolean.TRUE, + Boolean.FALSE, + Boolean.FALSE, + }; + + // properties + + /** Modify HTML element names: { "upper", "lower", "default" }. */ + protected static final String NAMES_ELEMS = "http://cyberneko.org/html/properties/names/elems"; + + /** Modify HTML attribute names: { "upper", "lower", "default" }. */ + protected static final String NAMES_ATTRS = "http://cyberneko.org/html/properties/names/attrs"; + + /** Default encoding. */ + protected static final String DEFAULT_ENCODING = "http://cyberneko.org/html/properties/default-encoding"; + + /** Error reporter. */ + protected static final String ERROR_REPORTER = "http://cyberneko.org/html/properties/error-reporter"; + + /** Doctype declaration public identifier. */ + protected static final String DOCTYPE_PUBID = "http://cyberneko.org/html/properties/doctype/pubid"; + + /** Doctype declaration system identifier. */ + protected static final String DOCTYPE_SYSID = "http://cyberneko.org/html/properties/doctype/sysid"; + + /** Recognized properties. */ + private static final String[] RECOGNIZED_PROPERTIES = { + NAMES_ELEMS, + NAMES_ATTRS, + DEFAULT_ENCODING, + ERROR_REPORTER, + DOCTYPE_PUBID, + DOCTYPE_SYSID, + }; + + /** Recognized properties defaults. */ + private static final Object[] RECOGNIZED_PROPERTIES_DEFAULTS = { + null, + null, + "Windows-1252", + null, + HTML_4_01_TRANSITIONAL_PUBID, + HTML_4_01_TRANSITIONAL_SYSID, + }; + + // states + + /** State: content. */ + protected static final short STATE_CONTENT = 0; + + /** State: markup bracket. */ + protected static final short STATE_MARKUP_BRACKET = 1; + + /** State: start document. */ + protected static final short STATE_START_DOCUMENT = 10; + + /** State: end document. */ + protected static final short STATE_END_DOCUMENT = 11; + + // modify HTML names + + /** Don't modify HTML names. */ + protected static final short NAMES_NO_CHANGE = 0; + + /** Uppercase HTML names. */ + protected static final short NAMES_UPPERCASE = 1; + + /** Lowercase HTML names. */ + protected static final short NAMES_LOWERCASE = 2; + + // defaults + + /** Default buffer size. */ + protected static final int DEFAULT_BUFFER_SIZE = 2048; + + // debugging + + /** Set to true to debug changes in the scanner. */ + private static final boolean DEBUG_SCANNER = false; + + /** Set to true to debug changes in the scanner state. */ + private static final boolean DEBUG_SCANNER_STATE = false; + + /** Set to true to debug the buffer. */ + private static final boolean DEBUG_BUFFER = false; + + /** Set to true to debug character encoding handling. */ + private static final boolean DEBUG_CHARSET = false; + + /** Set to true to debug callbacks. */ + protected static final boolean DEBUG_CALLBACKS = false; + + // static vars + + /** Synthesized event info item. */ + protected static final HTMLEventInfo SYNTHESIZED_ITEM = + new HTMLEventInfo.SynthesizedItem(); + + private final static BitSet ENTITY_CHARS = new BitSet(); + static { + final String str = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; + for (int i = 0; i < str.length(); ++i) { + char c = str.charAt(i); + ENTITY_CHARS.set(c); + } + } + // + // Data + // + + // features + + /** Augmentations. */ + protected boolean fAugmentations; + + /** Report errors. */ + protected boolean fReportErrors; + + /** Notify character entity references. */ + protected boolean fNotifyCharRefs; + + /** Notify XML built-in general entity references. */ + protected boolean fNotifyXmlBuiltinRefs; + + /** Notify HTML built-in general entity references. */ + protected boolean fNotifyHtmlBuiltinRefs; + + /** Fix Microsoft Windows® character entity references. */ + protected boolean fFixWindowsCharRefs; + + /** Strip CDATA delimiters from SCRIPT tags. */ + protected boolean fScriptStripCDATADelims; + + /** Strip comment delimiters from SCRIPT tags. */ + protected boolean fScriptStripCommentDelims; + + /** Strip CDATA delimiters from STYLE tags. */ + protected boolean fStyleStripCDATADelims; + + /** Strip comment delimiters from STYLE tags. */ + protected boolean fStyleStripCommentDelims; + + /** Ignore specified character set. */ + protected boolean fIgnoreSpecifiedCharset; + + /** CDATA sections. */ + protected boolean fCDATASections; + + /** Override doctype declaration public and system identifiers. */ + protected boolean fOverrideDoctype; + + /** Insert document type declaration. */ + protected boolean fInsertDoctype; + + /** Normalize attribute values. */ + protected boolean fNormalizeAttributes; + + /** Parse noscript content. */ + protected boolean fParseNoScriptContent; + + /** Parse noframes content. */ + protected boolean fParseNoFramesContent; + + /** Allows self closing iframe tags. */ + protected boolean fAllowSelfclosingIframe; + + /** Allows self closing tags. */ + protected boolean fAllowSelfclosingTags; + + // properties + + /** Modify HTML element names. */ + protected short fNamesElems; + + /** Modify HTML attribute names. */ + protected short fNamesAttrs; + + /** Default encoding. */ + protected String fDefaultIANAEncoding; + + /** Error reporter. */ + protected HTMLErrorReporter fErrorReporter; + + /** Doctype declaration public identifier. */ + protected String fDoctypePubid; + + /** Doctype declaration system identifier. */ + protected String fDoctypeSysid; + + // boundary locator information + + /** Beginning line number. */ + protected int fBeginLineNumber; + + /** Beginning column number. */ + protected int fBeginColumnNumber; + + /** Beginning character offset in the file. */ + protected int fBeginCharacterOffset; + + /** Ending line number. */ + protected int fEndLineNumber; + + /** Ending column number. */ + protected int fEndColumnNumber; + + /** Ending character offset in the file. */ + protected int fEndCharacterOffset; + + // state + + /** The playback byte stream. */ + protected PlaybackInputStream fByteStream; + + /** Current entity. */ + protected CurrentEntity fCurrentEntity; + + /** The current entity stack. */ + protected final Stack fCurrentEntityStack = new Stack(); + + /** The current scanner. */ + protected Scanner fScanner; + + /** The current scanner state. */ + protected short fScannerState; + + /** The document handler. */ + protected XMLDocumentHandler fDocumentHandler; + + /** Auto-detected IANA encoding. */ + protected String fIANAEncoding; + + /** Auto-detected Java encoding. */ + protected String fJavaEncoding; + + /** True if the encoding matches "ISO-8859-*". */ + protected boolean fIso8859Encoding; + + /** Element count. */ + protected int fElementCount; + + /** Element depth. */ + protected int fElementDepth; + + // scanners + + /** Content scanner. */ + protected Scanner fContentScanner = new ContentScanner(); + + /** + * Special scanner used for elements whose content needs to be scanned + * as plain text, ignoring markup such as elements and entity references. + * For example: <SCRIPT> and <COMMENT>. + */ + protected SpecialScanner fSpecialScanner = new SpecialScanner(); + + // temp vars + + /** String buffer. */ + protected final XMLStringBuffer fStringBuffer = new XMLStringBuffer(1024); + + /** String buffer. */ + private final XMLStringBuffer fStringBuffer2 = new XMLStringBuffer(1024); + + /** Non-normalized attribute string buffer. */ + private final XMLStringBuffer fNonNormAttr = new XMLStringBuffer(128); + + /** Augmentations. */ + private final HTMLAugmentations fInfosetAugs = new HTMLAugmentations(); + + /** Location infoset item. */ + private final LocationItem fLocationItem = new LocationItem(); + + /** Single boolean array. */ + private final boolean[] fSingleBoolean = { false }; + + /** Resource identifier. */ + private final XMLResourceIdentifierImpl fResourceId = new XMLResourceIdentifierImpl(); + + private final char REPLACEMENT_CHARACTER = '\uFFFD'; // the ïÂŋÂŊ character + + // + // Public methods + // + + /** + * Pushes an input source onto the current entity stack. This + * enables the scanner to transparently scan new content (e.g. + * the output written by an embedded script). At the end of the + * current entity, the scanner returns where it left off at the + * time this entity source was pushed. + *

+ * Note: + * This functionality is experimental at this time and is + * subject to change in future releases of NekoHTML. + * + * @param inputSource The new input source to start scanning. + * @see #evaluateInputSource(XMLInputSource) + */ + public void pushInputSource(XMLInputSource inputSource) { + final Reader reader = getReader(inputSource); + + fCurrentEntityStack.push(fCurrentEntity); + String encoding = inputSource.getEncoding(); + String publicId = inputSource.getPublicId(); + String baseSystemId = inputSource.getBaseSystemId(); + String literalSystemId = inputSource.getSystemId(); + String expandedSystemId = expandSystemId(literalSystemId, baseSystemId); + fCurrentEntity = new CurrentEntity(reader, encoding, + publicId, baseSystemId, + literalSystemId, expandedSystemId); + } // pushInputSource(XMLInputSource) + + private Reader getReader(final XMLInputSource inputSource) { + Reader reader = inputSource.getCharacterStream(); + if (reader == null) { + try { + return new InputStreamReader(inputSource.getByteStream(), fJavaEncoding); + } + catch (final UnsupportedEncodingException e) { + // should not happen as this encoding is already used to parse the "main" source + } + } + return reader; + } + + /** + * Immediately evaluates an input source and add the new content (e.g. + * the output written by an embedded script). + * + * @param inputSource The new input source to start evaluating. + * @see #pushInputSource(XMLInputSource) + */ + public void evaluateInputSource(XMLInputSource inputSource) { + final Scanner previousScanner = fScanner; + final short previousScannerState = fScannerState; + final CurrentEntity previousEntity = fCurrentEntity; + final Reader reader = getReader(inputSource); + + String encoding = inputSource.getEncoding(); + String publicId = inputSource.getPublicId(); + String baseSystemId = inputSource.getBaseSystemId(); + String literalSystemId = inputSource.getSystemId(); + String expandedSystemId = expandSystemId(literalSystemId, baseSystemId); + fCurrentEntity = new CurrentEntity(reader, encoding, + publicId, baseSystemId, + literalSystemId, expandedSystemId); + setScanner(fContentScanner); + setScannerState(STATE_CONTENT); + try { + do { + fScanner.scan(false); + } while (fScannerState != STATE_END_DOCUMENT); + } + catch (final IOException e) { + // ignore + } + setScanner(previousScanner); + setScannerState(previousScannerState); + fCurrentEntity = previousEntity; + } // evaluateInputSource(XMLInputSource) + + /** + * Cleans up used resources. For example, if scanning is terminated + * early, then this method ensures all remaining open streams are + * closed. + * + * @param closeall Close all streams, including the original. + * This is used in cases when the application has + * opened the original document stream and should + * be responsible for closing it. + */ + public void cleanup(boolean closeall) { + int size = fCurrentEntityStack.size(); + if (size > 0) { + // current entity is not the original, so close it + if (fCurrentEntity != null) { + fCurrentEntity.closeQuietly(); + } + // close remaining streams + for (int i = closeall ? 0 : 1; i < size; i++) { + fCurrentEntity = (CurrentEntity) fCurrentEntityStack.pop(); + fCurrentEntity.closeQuietly(); + } + } + else if (closeall && fCurrentEntity != null) { + fCurrentEntity.closeQuietly(); + } + } // cleanup(boolean) + + // + // XMLLocator methods + // + + /** Returns the encoding. */ + public String getEncoding() { + return fCurrentEntity != null ? fCurrentEntity.encoding : null; + } // getEncoding():String + + /** Returns the public identifier. */ + public String getPublicId() { + return fCurrentEntity != null ? fCurrentEntity.publicId : null; + } // getPublicId():String + + /** Returns the base system identifier. */ + public String getBaseSystemId() { + return fCurrentEntity != null ? fCurrentEntity.baseSystemId : null; + } // getBaseSystemId():String + + /** Returns the literal system identifier. */ + public String getLiteralSystemId() { + return fCurrentEntity != null ? fCurrentEntity.literalSystemId : null; + } // getLiteralSystemId():String + + /** Returns the expanded system identifier. */ + public String getExpandedSystemId() { + return fCurrentEntity != null ? fCurrentEntity.expandedSystemId : null; + } // getExpandedSystemId():String + + /** Returns the current line number. */ + public int getLineNumber() { + return fCurrentEntity != null ? fCurrentEntity.getLineNumber() : -1; + } // getLineNumber():int + + /** Returns the current column number. */ + public int getColumnNumber() { + return fCurrentEntity != null ? fCurrentEntity.getColumnNumber() : -1; + } // getColumnNumber():int + + /** Returns the XML version. */ + public String getXMLVersion() { + return fCurrentEntity != null ? fCurrentEntity.version : null; + } // getXMLVersion():String + + /** Returns the character offset. */ + public int getCharacterOffset() { + return fCurrentEntity != null ? fCurrentEntity.getCharacterOffset() : -1; + } // getCharacterOffset():int + + // + // HTMLComponent methods + // + + /** Returns the default state for a feature. */ + public Boolean getFeatureDefault(String featureId) { + int length = RECOGNIZED_FEATURES != null ? RECOGNIZED_FEATURES.length : 0; + for (int i = 0; i < length; i++) { + if (RECOGNIZED_FEATURES[i].equals(featureId)) { + return RECOGNIZED_FEATURES_DEFAULTS[i]; + } + } + return null; + } // getFeatureDefault(String):Boolean + + /** Returns the default state for a property. */ + public Object getPropertyDefault(String propertyId) { + int length = RECOGNIZED_PROPERTIES != null ? RECOGNIZED_PROPERTIES.length : 0; + for (int i = 0; i < length; i++) { + if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) { + return RECOGNIZED_PROPERTIES_DEFAULTS[i]; + } + } + return null; + } // getPropertyDefault(String):Object + + // + // XMLComponent methods + // + + /** Returns recognized features. */ + public String[] getRecognizedFeatures() { + return RECOGNIZED_FEATURES; + } // getRecognizedFeatures():String[] + + /** Returns recognized properties. */ + public String[] getRecognizedProperties() { + return RECOGNIZED_PROPERTIES; + } // getRecognizedProperties():String[] + + /** Resets the component. */ + public void reset(XMLComponentManager manager) + throws XMLConfigurationException { + + // get features + fAugmentations = manager.getFeature(AUGMENTATIONS); + fReportErrors = manager.getFeature(REPORT_ERRORS); + fNotifyCharRefs = manager.getFeature(NOTIFY_CHAR_REFS); + fNotifyXmlBuiltinRefs = manager.getFeature(NOTIFY_XML_BUILTIN_REFS); + fNotifyHtmlBuiltinRefs = manager.getFeature(NOTIFY_HTML_BUILTIN_REFS); + fFixWindowsCharRefs = manager.getFeature(FIX_MSWINDOWS_REFS); + fScriptStripCDATADelims = manager.getFeature(SCRIPT_STRIP_CDATA_DELIMS); + fScriptStripCommentDelims = manager.getFeature(SCRIPT_STRIP_COMMENT_DELIMS); + fStyleStripCDATADelims = manager.getFeature(STYLE_STRIP_CDATA_DELIMS); + fStyleStripCommentDelims = manager.getFeature(STYLE_STRIP_COMMENT_DELIMS); + fIgnoreSpecifiedCharset = manager.getFeature(IGNORE_SPECIFIED_CHARSET); + fCDATASections = manager.getFeature(CDATA_SECTIONS); + fOverrideDoctype = manager.getFeature(OVERRIDE_DOCTYPE); + fInsertDoctype = manager.getFeature(INSERT_DOCTYPE); + fNormalizeAttributes = manager.getFeature(NORMALIZE_ATTRIBUTES); + fParseNoScriptContent = manager.getFeature(PARSE_NOSCRIPT_CONTENT); + fAllowSelfclosingIframe = manager.getFeature(ALLOW_SELFCLOSING_IFRAME); + fAllowSelfclosingTags = manager.getFeature(ALLOW_SELFCLOSING_TAGS); + + // get properties + fNamesElems = getNamesValue(String.valueOf(manager.getProperty(NAMES_ELEMS))); + fNamesAttrs = getNamesValue(String.valueOf(manager.getProperty(NAMES_ATTRS))); + fDefaultIANAEncoding = String.valueOf(manager.getProperty(DEFAULT_ENCODING)); + fErrorReporter = (HTMLErrorReporter)manager.getProperty(ERROR_REPORTER); + fDoctypePubid = String.valueOf(manager.getProperty(DOCTYPE_PUBID)); + fDoctypeSysid = String.valueOf(manager.getProperty(DOCTYPE_SYSID)); + + } // reset(XMLComponentManager) + + /** Sets a feature. */ + public void setFeature(final String featureId, final boolean state) { + + if (featureId.equals(AUGMENTATIONS)) { + fAugmentations = state; + } + else if (featureId.equals(IGNORE_SPECIFIED_CHARSET)) { + fIgnoreSpecifiedCharset = state; + } + else if (featureId.equals(NOTIFY_CHAR_REFS)) { + fNotifyCharRefs = state; + } + else if (featureId.equals(NOTIFY_XML_BUILTIN_REFS)) { + fNotifyXmlBuiltinRefs = state; + } + else if (featureId.equals(NOTIFY_HTML_BUILTIN_REFS)) { + fNotifyHtmlBuiltinRefs = state; + } + else if (featureId.equals(FIX_MSWINDOWS_REFS)) { + fFixWindowsCharRefs = state; + } + else if (featureId.equals(SCRIPT_STRIP_CDATA_DELIMS)) { + fScriptStripCDATADelims = state; + } + else if (featureId.equals(SCRIPT_STRIP_COMMENT_DELIMS)) { + fScriptStripCommentDelims = state; + } + else if (featureId.equals(STYLE_STRIP_CDATA_DELIMS)) { + fStyleStripCDATADelims = state; + } + else if (featureId.equals(STYLE_STRIP_COMMENT_DELIMS)) { + fStyleStripCommentDelims = state; + } + else if (featureId.equals(PARSE_NOSCRIPT_CONTENT)) { + fParseNoScriptContent = state; + } + else if (featureId.equals(ALLOW_SELFCLOSING_IFRAME)) { + fAllowSelfclosingIframe = state; + } + else if (featureId.equals(ALLOW_SELFCLOSING_TAGS)) { + fAllowSelfclosingTags = state; + } + + } // setFeature(String,boolean) + + /** Sets a property. */ + public void setProperty(String propertyId, Object value) + throws XMLConfigurationException { + + if (propertyId.equals(NAMES_ELEMS)) { + fNamesElems = getNamesValue(String.valueOf(value)); + return; + } + + if (propertyId.equals(NAMES_ATTRS)) { + fNamesAttrs = getNamesValue(String.valueOf(value)); + return; + } + + if (propertyId.equals(DEFAULT_ENCODING)) { + fDefaultIANAEncoding = String.valueOf(value); + return; + } + + } // setProperty(String,Object) + + // + // XMLDocumentScanner methods + // + + /** Sets the input source. */ + public void setInputSource(XMLInputSource source) throws IOException { + + // reset state + fElementCount = 0; + fElementDepth = -1; + fByteStream = null; + fCurrentEntityStack.removeAllElements(); + + fBeginLineNumber = 1; + fBeginColumnNumber = 1; + fBeginCharacterOffset = 0; + fEndLineNumber = fBeginLineNumber; + fEndColumnNumber = fBeginColumnNumber; + fEndCharacterOffset = fBeginCharacterOffset; + + // reset encoding information + fIANAEncoding = fDefaultIANAEncoding; + fJavaEncoding = fIANAEncoding; + + // get location information + String encoding = source.getEncoding(); + String publicId = source.getPublicId(); + String baseSystemId = source.getBaseSystemId(); + String literalSystemId = source.getSystemId(); + String expandedSystemId = expandSystemId(literalSystemId, baseSystemId); + + // open stream + Reader reader = source.getCharacterStream(); + if (reader == null) { + InputStream inputStream = source.getByteStream(); + if (inputStream == null) { + URL url = new URL(expandedSystemId); + inputStream = url.openStream(); + } + fByteStream = new PlaybackInputStream(inputStream); + String[] encodings = new String[2]; + if (encoding == null) { + fByteStream.detectEncoding(encodings); + } + else { + encodings[0] = encoding; + } + if (encodings[0] == null) { + encodings[0] = fDefaultIANAEncoding; + if (fReportErrors) { + fErrorReporter.reportWarning("HTML1000", null); + } + } + if (encodings[1] == null) { + encodings[1] = EncodingMap.getIANA2JavaMapping(encodings[0].toUpperCase(Locale.ENGLISH)); + if (encodings[1] == null) { + encodings[1] = encodings[0]; + if (fReportErrors) { + fErrorReporter.reportWarning("HTML1001", new Object[]{encodings[0]}); + } + } + } + fIANAEncoding = encodings[0]; + fJavaEncoding = encodings[1]; + /* PATCH: Asgeir Asgeirsson */ + fIso8859Encoding = fIANAEncoding == null + || fIANAEncoding.toUpperCase(Locale.ENGLISH).startsWith("ISO-8859") + || fIANAEncoding.equalsIgnoreCase(fDefaultIANAEncoding); + encoding = fIANAEncoding; + reader = new InputStreamReader(fByteStream, fJavaEncoding); + } + fCurrentEntity = new CurrentEntity(reader, encoding, + publicId, baseSystemId, + literalSystemId, expandedSystemId); + + // set scanner and state + setScanner(fContentScanner); + setScannerState(STATE_START_DOCUMENT); + + } // setInputSource(XMLInputSource) + + /** Scans the document. */ + public boolean scanDocument(boolean complete) throws XNIException, IOException { + do { + if (!fScanner.scan(complete)) { + return false; + } + } while (complete); + return true; + } // scanDocument(boolean):boolean + + /** Sets the document handler. */ + public void setDocumentHandler(XMLDocumentHandler handler) { + fDocumentHandler = handler; + } // setDocumentHandler(XMLDocumentHandler) + + // @since Xerces 2.1.0 + + /** Returns the document handler. */ + public XMLDocumentHandler getDocumentHandler() { + return fDocumentHandler; + } // getDocumentHandler():XMLDocumentHandler + + // + // Protected static methods + // + + /** Returns the value of the specified attribute, ignoring case. */ + protected static String getValue(XMLAttributes attrs, String aname) { + int length = attrs != null ? attrs.getLength() : 0; + for (int i = 0; i < length; i++) { + if (attrs.getQName(i).equalsIgnoreCase(aname)) { + return attrs.getValue(i); + } + } + return null; + } // getValue(XMLAttributes,String):String + + /** + * Expands a system id and returns the system id as a URI, if + * it can be expanded. A return value of null means that the + * identifier is already expanded. An exception thrown + * indicates a failure to expand the id. + * + * @param systemId The systemId to be expanded. + * + * @return Returns the URI string representing the expanded system + * identifier. A null value indicates that the given + * system identifier is already expanded. + * + */ + public static String expandSystemId(String systemId, String baseSystemId) { + + // check for bad parameters id + if (systemId == null || systemId.length() == 0) { + return systemId; + } + // if id already expanded, return + try { + URI uri = new URI(systemId); + if (uri != null) { + return systemId; + } + } + catch (URI.MalformedURIException e) { + // continue on... + } + // normalize id + String id = fixURI(systemId); + + // normalize base + URI base = null; + URI uri = null; + try { + if (baseSystemId == null || baseSystemId.length() == 0 || + baseSystemId.equals(systemId)) { + String dir; + try { + dir = fixURI(System.getProperty("user.dir")); + } + catch (SecurityException se) { + dir = ""; + } + if (!dir.endsWith("/")) { + dir = dir + "/"; + } + base = new URI("file", "", dir, null, null); + } + else { + try { + base = new URI(fixURI(baseSystemId)); + } + catch (URI.MalformedURIException e) { + String dir; + try { + dir = fixURI(System.getProperty("user.dir")); + } + catch (SecurityException se) { + dir = ""; + } + if (baseSystemId.indexOf(':') != -1) { + // for xml schemas we might have baseURI with + // a specified drive + base = new URI("file", "", fixURI(baseSystemId), null, null); + } + else { + if (!dir.endsWith("/")) { + dir = dir + "/"; + } + dir = dir + fixURI(baseSystemId); + base = new URI("file", "", dir, null, null); + } + } + } + // expand id + uri = new URI(base, id); + } + catch (URI.MalformedURIException e) { + // let it go through + } + + if (uri == null) { + return systemId; + } + return uri.toString(); + + } // expandSystemId(String,String):String + + /** + * Fixes a platform dependent filename to standard URI form. + * + * @param str The string to fix. + * + * @return Returns the fixed URI string. + */ + protected static String fixURI(String str) { + + // handle platform dependent strings + str = str.replace(java.io.File.separatorChar, '/'); + + // Windows fix + if (str.length() >= 2) { + char ch1 = str.charAt(1); + // change "C:blah" to "/C:blah" + if (ch1 == ':') { + final char ch0 = String.valueOf(str.charAt(0)).toUpperCase(Locale.ENGLISH).charAt(0); + if (ch0 >= 'A' && ch0 <= 'Z') { + str = "/" + str; + } + } + // change "//blah" to "file://blah" + else if (ch1 == '/' && str.charAt(0) == '/') { + str = "file:" + str; + } + } + + // done + return str; + + } // fixURI(String):String + + /** Modifies the given name based on the specified mode. */ + protected static final String modifyName(String name, short mode) { + switch (mode) { + case NAMES_UPPERCASE: return name.toUpperCase(Locale.ENGLISH); + case NAMES_LOWERCASE: return name.toLowerCase(Locale.ENGLISH); + } + return name; + } // modifyName(String,short):String + + /** + * Converts HTML names string value to constant value. + * + * @see #NAMES_NO_CHANGE + * @see #NAMES_LOWERCASE + * @see #NAMES_UPPERCASE + */ + protected static final short getNamesValue(String value) { + if (value.equals("lower")) { + return NAMES_LOWERCASE; + } + if (value.equals("upper")) { + return NAMES_UPPERCASE; + } + return NAMES_NO_CHANGE; + } // getNamesValue(String):short + + /** + * Fixes Microsoft Windows® specific characters. + *

+ * Details about this common problem can be found at + * http://www.cs.tut.fi/~jkorpela/www/windows-chars.html + */ + protected int fixWindowsCharacter(int origChar) { + /* PATCH: Asgeir Asgeirsson */ + switch(origChar) { + case 130: return 8218; + case 131: return 402; + case 132: return 8222; + case 133: return 8230; + case 134: return 8224; + case 135: return 8225; + case 136: return 710; + case 137: return 8240; + case 138: return 352; + case 139: return 8249; + case 140: return 338; + case 145: return 8216; + case 146: return 8217; + case 147: return 8220; + case 148: return 8221; + case 149: return 8226; + case 150: return 8211; + case 151: return 8212; + case 152: return 732; + case 153: return 8482; + case 154: return 353; + case 155: return 8250; + case 156: return 339; + case 159: return 376; + } + return origChar; + } // fixWindowsCharacter(int):int + + // + // Protected methods + // + + // i/o + /** Reads a single character. */ + protected int read() throws IOException { + return fCurrentEntity.read(); + } + + + // debugging + + /** Sets the scanner. */ + protected void setScanner(Scanner scanner) { + fScanner = scanner; + if (DEBUG_SCANNER) { + System.out.print("$$$ setScanner("); + System.out.print(scanner!=null?scanner.getClass().getName():"null"); + System.out.println(");"); + } + } // setScanner(Scanner) + + /** Sets the scanner state. */ + protected void setScannerState(short state) { + fScannerState = state; + if (DEBUG_SCANNER_STATE) { + System.out.print("$$$ setScannerState("); + switch (fScannerState) { + case STATE_CONTENT: { System.out.print("STATE_CONTENT"); break; } + case STATE_MARKUP_BRACKET: { System.out.print("STATE_MARKUP_BRACKET"); break; } + case STATE_START_DOCUMENT: { System.out.print("STATE_START_DOCUMENT"); break; } + case STATE_END_DOCUMENT: { System.out.print("STATE_END_DOCUMENT"); break; } + } + System.out.println(");"); + } + } // setScannerState(short) + + // scanning + + /** Scans a DOCTYPE line. */ + protected void scanDoctype() throws IOException { + String root = null; + String pubid = null; + String sysid = null; + + if (skipSpaces()) { + root = scanName(true); + if (root == null) { + if (fReportErrors) { + fErrorReporter.reportError("HTML1014", null); + } + } + else { + root = modifyName(root, fNamesElems); + } + if (skipSpaces()) { + if (skip("PUBLIC", false)) { + skipSpaces(); + pubid = scanLiteral(); + if (skipSpaces()) { + sysid = scanLiteral(); + } + } + else if (skip("SYSTEM", false)) { + skipSpaces(); + sysid = scanLiteral(); + } + } + } + int c; + while ((c = fCurrentEntity.read()) != -1) { + if (c == '<') { + fCurrentEntity.rewind(); + break; + } + if (c == '>') { + break; + } + if (c == '[') { + skipMarkup(true); + break; + } + } + + if (fDocumentHandler != null) { + if (fOverrideDoctype) { + pubid = fDoctypePubid; + sysid = fDoctypeSysid; + } + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + fDocumentHandler.doctypeDecl(root, pubid, sysid, locationAugs()); + } + + } // scanDoctype() + + /** Scans a quoted literal. */ + protected String scanLiteral() throws IOException { + int quote = fCurrentEntity.read(); + if (quote == '\'' || quote == '"') { + StringBuffer str = new StringBuffer(); + int c; + while ((c = fCurrentEntity.read()) != -1) { + if (c == quote) { + break; + } + if (c == '\r' || c == '\n') { + fCurrentEntity.rewind(); + // NOTE: This collapses newlines to a single space. + // [Q] Is this the right thing to do here? -Ac + skipNewlines(); + str.append(' '); + } + else if (c == '<') { + fCurrentEntity.rewind(); + break; + } + else { + appendChar(str, c); + } + } + if (c == -1) { + if (fReportErrors) { + fErrorReporter.reportError("HTML1007", null); + } + throw new EOFException(); + } + return str.toString(); + } + fCurrentEntity.rewind(); + return null; + } // scanLiteral():String + + /** Scans a name. */ + protected String scanName(final boolean strict) throws IOException { + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded("(scanName: "); + } + if (fCurrentEntity.offset == fCurrentEntity.length) { + if (fCurrentEntity.load(0) == -1) { + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")scanName: "); + } + return null; + } + } + int offset = fCurrentEntity.offset; + while (true) { + while (fCurrentEntity.hasNext()) { + char c = fCurrentEntity.getNextChar(); + if ((strict && (!Character.isLetterOrDigit(c) && c != '-' && c != '.' && c != ':' && c != '_')) + || (!strict && (Character.isWhitespace(c) || c == '=' || c == '/' || c == '>'))) { + fCurrentEntity.rewind(); + break; + } + } + if (fCurrentEntity.offset == fCurrentEntity.length) { + int length = fCurrentEntity.length - offset; + System.arraycopy(fCurrentEntity.buffer, offset, fCurrentEntity.buffer, 0, length); + int count = fCurrentEntity.load(length); + offset = 0; + if (count == -1) { + break; + } + } + else { + break; + } + } + int length = fCurrentEntity.offset - offset; + String name = length > 0 ? new String(fCurrentEntity.buffer, offset, length) : null; + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")scanName: ", " -> \"" + name + '"'); + } + return name; + } // scanName():String + + /** Scans an entity reference. */ + protected int scanEntityRef(final XMLStringBuffer str, final boolean content) + throws IOException { + str.clear(); + str.append('&'); + boolean endsWithSemicolon = false; + while (true) { + int c = fCurrentEntity.read(); + if (c == ';') { + str.append(';'); + endsWithSemicolon = true; + break; + } + else if (c == -1) { + break; + } + else if (!ENTITY_CHARS.get(c) && c != '#') { + fCurrentEntity.rewind(); + break; + } + appendChar(str, c); + } + + if (!endsWithSemicolon) { + if (fReportErrors) { + fErrorReporter.reportWarning("HTML1004", null); + } + } + if (str.length == 1) { + if (content && fDocumentHandler != null && fElementCount >= fElementDepth) { + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + fDocumentHandler.characters(str, locationAugs()); + } + return -1; + } + + final String name; + if (endsWithSemicolon) + name = str.toString().substring(1, str.length -1); + else + name = str.toString().substring(1); + + if (name.startsWith("#")) { + int value = -1; + try { + if (name.startsWith("#x") || name.startsWith("#X")) { + value = Integer.parseInt(name.substring(2), 16); + } + else { + value = Integer.parseInt(name.substring(1)); + } + /* PATCH: Asgeir Asgeirsson */ + if (fFixWindowsCharRefs && fIso8859Encoding) { + value = fixWindowsCharacter(value); + } + if (content && fDocumentHandler != null && fElementCount >= fElementDepth) { + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + if (fNotifyCharRefs) { + XMLResourceIdentifier id = resourceId(); + String encoding = null; + fDocumentHandler.startGeneralEntity(name, id, encoding, locationAugs()); + } + str.clear(); + try { + appendChar(str, value); + } + catch (final IllegalArgumentException e) { // when value is not valid as UTF-16 + if (fReportErrors) { + fErrorReporter.reportError("HTML1005", new Object[]{name}); + } + str.append(REPLACEMENT_CHARACTER); + } + fDocumentHandler.characters(str, locationAugs()); + if (fNotifyCharRefs) { + fDocumentHandler.endGeneralEntity(name, locationAugs()); + } + } + } + catch (NumberFormatException e) { + if (fReportErrors) { + fErrorReporter.reportError("HTML1005", new Object[]{name}); + } + if (content && fDocumentHandler != null && fElementCount >= fElementDepth) { + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + fDocumentHandler.characters(str, locationAugs()); + } + } + return value; + } + + int c = HTMLEntities.get(name); + // in attributes, some incomplete entities should be recognized, not all + // TODO: investigate to find which ones (there are differences between browsers) + // in a first time, consider only those that behave the same in FF and IE + final boolean invalidEntityInAttribute = !content && !endsWithSemicolon && c > 256; + if (c == -1 || invalidEntityInAttribute) { + if (fReportErrors) { + fErrorReporter.reportWarning("HTML1006", new Object[]{name}); + } + if (content && fDocumentHandler != null && fElementCount >= fElementDepth) { + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + fDocumentHandler.characters(str, locationAugs()); + } + return -1; + } + if (content && fDocumentHandler != null && fElementCount >= fElementDepth) { + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + boolean notify = fNotifyHtmlBuiltinRefs || (fNotifyXmlBuiltinRefs && builtinXmlRef(name)); + if (notify) { + XMLResourceIdentifier id = resourceId(); + String encoding = null; + fDocumentHandler.startGeneralEntity(name, id, encoding, locationAugs()); + } + str.clear(); + appendChar(str, c); + fDocumentHandler.characters(str, locationAugs()); + if (notify) { + fDocumentHandler.endGeneralEntity(name, locationAugs()); + } + } + return c; + + } // scanEntityRef(XMLStringBuffer,boolean):int + + /** Returns true if the specified text is present and is skipped. */ + protected boolean skip(String s, boolean caseSensitive) throws IOException { + int length = s != null ? s.length() : 0; + for (int i = 0; i < length; i++) { + if (fCurrentEntity.offset == fCurrentEntity.length) { + System.arraycopy(fCurrentEntity.buffer, fCurrentEntity.offset - i, fCurrentEntity.buffer, 0, i); + if (fCurrentEntity.load(i) == -1) { + fCurrentEntity.offset = 0; + return false; + } + } + char c0 = s.charAt(i); + char c1 = fCurrentEntity.getNextChar(); + if (!caseSensitive) { + c0 = String.valueOf(c0).toUpperCase(Locale.ENGLISH).charAt(0); + c1 = String.valueOf(c1).toUpperCase(Locale.ENGLISH).charAt(0); + } + if (c0 != c1) { + fCurrentEntity.rewind(i + 1); + return false; + } + } + return true; + } // skip(String):boolean + + /** Skips markup. */ + protected boolean skipMarkup(boolean balance) throws IOException { + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded("(skipMarkup: "); + } + int depth = 1; + boolean slashgt = false; + OUTER: while (true) { + if (fCurrentEntity.offset == fCurrentEntity.length) { + if (fCurrentEntity.load(0) == -1) { + break OUTER; + } + } + while (fCurrentEntity.hasNext()) { + char c = fCurrentEntity.getNextChar(); + if (balance && c == '<') { + depth++; + } + else if (c == '>') { + depth--; + if (depth == 0) { + break OUTER; + } + } + else if (c == '/') { + if (fCurrentEntity.offset == fCurrentEntity.length) { + if (fCurrentEntity.load(0) == -1) { + break OUTER; + } + } + c = fCurrentEntity.getNextChar(); + if (c == '>') { + slashgt = true; + depth--; + if (depth == 0) { + break OUTER; + } + } + else { + fCurrentEntity.rewind(); + } + } + else if (c == '\r' || c == '\n') { + fCurrentEntity.rewind(); + skipNewlines(); + } + } + } + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")skipMarkup: ", " -> " + slashgt); + } + return slashgt; + } // skipMarkup():boolean + + /** Skips whitespace. */ + protected boolean skipSpaces() throws IOException { + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded("(skipSpaces: "); + } + boolean spaces = false; + while (true) { + if (fCurrentEntity.offset == fCurrentEntity.length) { + if (fCurrentEntity.load(0) == -1) { + break; + } + } + char c = fCurrentEntity.getNextChar(); + if (!Character.isWhitespace(c)) { + fCurrentEntity.rewind(); + break; + } + spaces = true; + if (c == '\r' || c == '\n') { + fCurrentEntity.rewind(); + skipNewlines(); + continue; + } + } + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")skipSpaces: ", " -> " + spaces); + } + return spaces; + } // skipSpaces() + + /** Skips newlines and returns the number of newlines skipped. */ + protected int skipNewlines() throws IOException { + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded("(skipNewlines: "); + } + + if (!fCurrentEntity.hasNext()) { + if (fCurrentEntity.load(0) == -1) { + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")skipNewlines: "); + } + return 0; + } + } + char c = fCurrentEntity.getCurrentChar(); + int newlines = 0; + int offset = fCurrentEntity.offset; + if (c == '\n' || c == '\r') { + do { + c = fCurrentEntity.getNextChar(); + if (c == '\r') { + newlines++; + if (fCurrentEntity.offset == fCurrentEntity.length) { + offset = 0; + fCurrentEntity.offset = newlines; + if (fCurrentEntity.load(newlines) == -1) { + break; + } + } + if (fCurrentEntity.getCurrentChar() == '\n') { + fCurrentEntity.offset++; + fCurrentEntity.characterOffset_++; + offset++; + } + } + else if (c == '\n') { + newlines++; + if (fCurrentEntity.offset == fCurrentEntity.length) { + offset = 0; + fCurrentEntity.offset = newlines; + if (fCurrentEntity.load(newlines) == -1) { + break; + } + } + } + else { + fCurrentEntity.rewind(); + break; + } + } while (fCurrentEntity.offset < fCurrentEntity.length - 1); + fCurrentEntity.incLine(newlines); + } + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")skipNewlines: ", " -> " + newlines); + } + return newlines; + } // skipNewlines(int):int + + // infoset utility methods + + /** Returns an augmentations object with a location item added. */ + protected final Augmentations locationAugs() { + HTMLAugmentations augs = null; + if (fAugmentations) { + fLocationItem.setValues(fBeginLineNumber, fBeginColumnNumber, + fBeginCharacterOffset, fEndLineNumber, + fEndColumnNumber, fEndCharacterOffset); + augs = fInfosetAugs; + augs.removeAllItems(); + augs.putItem(AUGMENTATIONS, fLocationItem); + } + return augs; + } // locationAugs():Augmentations + + /** Returns an augmentations object with a synthesized item added. */ + protected final Augmentations synthesizedAugs() { + HTMLAugmentations augs = null; + if (fAugmentations) { + augs = fInfosetAugs; + augs.removeAllItems(); + augs.putItem(AUGMENTATIONS, SYNTHESIZED_ITEM); + } + return augs; + } // synthesizedAugs():Augmentations + + /** Returns an empty resource identifier. */ + protected final XMLResourceIdentifier resourceId() { + /***/ + fResourceId.clear(); + return fResourceId; + /*** + // NOTE: Unfortunately, the Xerces DOM parser classes expect a + // non-null resource identifier object to be passed to + // startGeneralEntity. -Ac + return null; + /***/ + } // resourceId():XMLResourceIdentifier + + // + // Protected static methods + // + + /** Returns true if the name is a built-in XML general entity reference. */ + protected static boolean builtinXmlRef(String name) { + return name.equals("amp") || name.equals("lt") || name.equals("gt") || + name.equals("quot") || name.equals("apos"); + } // builtinXmlRef(String):boolean + + // + // Private methods + // + + /** + * Append a character to an XMLStringBuffer. The character is an int value, and can either be a + * single UTF-16 character or a supplementary character represented by two UTF-16 code points. + * + * @param str The XMLStringBuffer to append to. + * @param value The character value. + */ + private void appendChar( XMLStringBuffer str, int value ) + { + if ( value > Character.MAX_VALUE ) + { + char[] chars = Character.toChars( value ); + + str.append( chars, 0, chars.length ); + } + else + { + str.append( (char) value ); + } + } + + /** + * Append a character to a StringBuffer. The character is an int value, and can either be a + * single UTF-16 character or a supplementary character represented by two UTF-16 code points. + * + * @param str The StringBuffer to append to. + * @param value The character value. + */ + private void appendChar( StringBuffer str, int value ) + { + if ( value > Character.MAX_VALUE ) + { + char[] chars = Character.toChars( value ); + + str.append( chars, 0, chars.length ); + } + else + { + str.append( (char) value ); + } + } + + // + // Interfaces + // + + /** + * Basic scanner interface. + * + * @author Andy Clark + */ + public interface Scanner { + + // + // Scanner methods + // + + /** + * Scans part of the document. This interface allows scanning to + * be performed in a pulling manner. + * + * @param complete True if the scanner should not return until + * scanning is complete. + * + * @return True if additional scanning is required. + * + * @throws IOException Thrown if I/O error occurs. + */ + public boolean scan(boolean complete) throws IOException; + + } // interface Scanner + + // + // Classes + // + + /** + * Current entity. + * + * @author Andy Clark + */ + public static class CurrentEntity { + + // + // Data + // + + /** Character stream. */ + private Reader stream_; + + /** Encoding. */ + private String encoding; + + /** Public identifier. */ + public final String publicId; + + /** Base system identifier. */ + public final String baseSystemId; + + /** Literal system identifier. */ + public final String literalSystemId; + + /** Expanded system identifier. */ + public final String expandedSystemId; + + /** XML version. */ + public final String version = "1.0"; + + /** Line number. */ + private int lineNumber_ = 1; + + /** Column number. */ + private int columnNumber_ = 1; + + /** Character offset in the file. */ + public int characterOffset_ = 0; + + // buffer + + /** Character buffer. */ + public char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + + /** Offset into character buffer. */ + public int offset = 0; + + /** Length of characters read into character buffer. */ + public int length = 0; + + private boolean endReached_ = false; + + // + // Constructors + // + + /** Constructs an entity from the specified stream. */ + public CurrentEntity(Reader stream, String encoding, + String publicId, String baseSystemId, + String literalSystemId, String expandedSystemId) { + stream_ = stream; + this.encoding = encoding; + this.publicId = publicId; + this.baseSystemId = baseSystemId; + this.literalSystemId = literalSystemId; + this.expandedSystemId = expandedSystemId; + } // (Reader,String,String,String,String) + + private char getCurrentChar() { + return buffer[offset]; + } + + /** + * Gets the current character and moves to next one. + * @return + */ + private char getNextChar() { + characterOffset_++; + columnNumber_++; + return buffer[offset++]; + } + private void closeQuietly() { + try { + stream_.close(); + } + catch (IOException e) { + // ignore + } + } + + /** + * Indicates if there are characters left. + */ + boolean hasNext() { + return offset < length; + } + + /** + * Loads a new chunk of data into the buffer and returns the number of + * characters loaded or -1 if no additional characters were loaded. + * + * @param offset The offset at which new characters should be loaded. + */ + protected int load(int offset) throws IOException { + if (DEBUG_BUFFER) { + debugBufferIfNeeded("(load: "); + } + // resize buffer, if needed + if (offset == buffer.length) { + int adjust = buffer.length / 4; + char[] array = new char[buffer.length + adjust]; + System.arraycopy(buffer, 0, array, 0, length); + buffer = array; + } + // read a block of characters + int count = stream_.read(buffer, offset, buffer.length - offset); + if (count == -1) { + endReached_ = true; + } + length = count != -1 ? count + offset : offset; + this.offset = offset; + if (DEBUG_BUFFER) { + debugBufferIfNeeded(")load: ", " -> " + count); + } + return count; + } // load():int + + /** Reads a single character. */ + protected int read() throws IOException { + if (DEBUG_BUFFER) { + debugBufferIfNeeded("(read: "); + } + if (offset == length) { + if (endReached_) { + return -1; + } + if (load(0) == -1) { + if (DEBUG_BUFFER) { + System.out.println(")read: -> -1"); + } + return -1; + } + } + final char c = buffer[offset++]; + characterOffset_++; + columnNumber_++; + + if (DEBUG_BUFFER) { + debugBufferIfNeeded(")read: ", " -> " + c); + } + return c; + } // read():int + + /** Prints the contents of the character buffer to standard out. */ + private void debugBufferIfNeeded(final String prefix) { + debugBufferIfNeeded(prefix, ""); + } + /** Prints the contents of the character buffer to standard out. */ + private void debugBufferIfNeeded(final String prefix, final String suffix) { + if (DEBUG_BUFFER) { + System.out.print(prefix); + System.out.print('['); + System.out.print(length); + System.out.print(' '); + System.out.print(offset); + if (length > 0) { + System.out.print(" \""); + for (int i = 0; i < length; i++) { + if (i == offset) { + System.out.print('^'); + } + char c = buffer[i]; + switch (c) { + case '\r': { + System.out.print("\\r"); + break; + } + case '\n': { + System.out.print("\\n"); + break; + } + case '\t': { + System.out.print("\\t"); + break; + } + case '"': { + System.out.print("\\\""); + break; + } + default: { + System.out.print(c); + } + } + } + if (offset == length) { + System.out.print('^'); + } + System.out.print('"'); + } + System.out.print(']'); + System.out.print(suffix); + System.out.println(); + } + } // printBuffer() + + private void setStream(final InputStreamReader inputStreamReader) { + stream_ = inputStreamReader; + offset = length = characterOffset_ = 0; + lineNumber_ = columnNumber_ = 1; + encoding = inputStreamReader.getEncoding(); + } + + /** + * Goes back, cancelling the effect of the previous read() call. + */ + private void rewind() { + offset--; + characterOffset_--; + columnNumber_--; + } + private void rewind(int i) { + offset -= i; + characterOffset_ -= i; + columnNumber_ -= i; + } + + private void incLine() { + lineNumber_++; + columnNumber_ = 1; + } + + private void incLine(int nbLines) { + lineNumber_ += nbLines; + columnNumber_ = 1; + } + + public int getLineNumber() { + return lineNumber_; + } + + private void resetBuffer(final XMLStringBuffer buffer, final int lineNumber, + final int columnNumber, final int characterOffset) { + lineNumber_ = lineNumber; + columnNumber_ = columnNumber; + this.characterOffset_ = characterOffset; + this.buffer = buffer.ch; + this.offset = buffer.offset; + this.length = buffer.length; + } + + private int getColumnNumber() { + return columnNumber_; + } + + private void restorePosition(int originalOffset, + int originalColumnNumber, int originalCharacterOffset) { + this.offset = originalOffset; + this.columnNumber_ = originalColumnNumber; + this.characterOffset_ = originalCharacterOffset; + } + + private int getCharacterOffset() { + return characterOffset_; + } + } // class CurrentEntity + + /** + * The primary HTML document scanner. + * + * @author Andy Clark + */ + public class ContentScanner + implements Scanner { + + // + // Data + // + + // temp vars + + /** A qualified name. */ + private final QName fQName = new QName(); + + /** Attributes. */ + private final XMLAttributesImpl fAttributes = new XMLAttributesImpl(); + + // + // Scanner methods + // + + /** Scan. */ + public boolean scan(boolean complete) throws IOException { + boolean next; + do { + try { + next = false; + switch (fScannerState) { + case STATE_CONTENT: { + fBeginLineNumber = fCurrentEntity.getLineNumber(); + fBeginColumnNumber = fCurrentEntity.getColumnNumber(); + fBeginCharacterOffset = fCurrentEntity.getCharacterOffset(); + int c = fCurrentEntity.read(); + if (c == '<') { + setScannerState(STATE_MARKUP_BRACKET); + next = true; + } + else if (c == '&') { + scanEntityRef(fStringBuffer, true); + } + else if (c == -1) { + throw new EOFException(); + } + else { + fCurrentEntity.rewind(); + scanCharacters(); + } + break; + } + case STATE_MARKUP_BRACKET: { + int c = fCurrentEntity.read(); + if (c == '!') { + if (skip("--", false)) { + scanComment(); + } + else if (skip("[CDATA[", false)) { + scanCDATA(); + } + else if (skip("DOCTYPE", false)) { + scanDoctype(); + } + else { + if (fReportErrors) { + fErrorReporter.reportError("HTML1002", null); + } + skipMarkup(true); + } + } + else if (c == '?') { + scanPI(); + } + else if (c == '/') { + scanEndElement(); + } + else if (c == -1) { + if (fReportErrors) { + fErrorReporter.reportError("HTML1003", null); + } + if (fDocumentHandler != null && fElementCount >= fElementDepth) { + fStringBuffer.clear(); + fStringBuffer.append('<'); + fDocumentHandler.characters(fStringBuffer, null); + } + throw new EOFException(); + } + else { + fCurrentEntity.rewind(); + fElementCount++; + fSingleBoolean[0] = false; + final String ename = scanStartElement(fSingleBoolean); + final String enameLC = ename == null ? null : ename.toLowerCase(); + fBeginLineNumber = fCurrentEntity.getLineNumber(); + fBeginColumnNumber = fCurrentEntity.getColumnNumber(); + fBeginCharacterOffset = fCurrentEntity.getCharacterOffset(); + if ("script".equals(enameLC)) { + scanScriptContent(); + } + else if (!fAllowSelfclosingTags && !fAllowSelfclosingIframe && "iframe".equals(enameLC)) { + scanUntilEndTag("iframe"); + } + else if (!fParseNoScriptContent && "noscript".equals(enameLC)) { + scanUntilEndTag("noscript"); + } + else if (!fParseNoFramesContent && "noframes".equals(enameLC)) { + scanUntilEndTag("noframes"); + } + else if (ename != null && !fSingleBoolean[0] + && HTMLElements.getElement(enameLC).isSpecial() + && (!ename.equalsIgnoreCase("TITLE") || isEnded(enameLC))) { + setScanner(fSpecialScanner.setElementName(ename)); + setScannerState(STATE_CONTENT); + return true; + } + } + setScannerState(STATE_CONTENT); + break; + } + case STATE_START_DOCUMENT: { + if (fDocumentHandler != null && fElementCount >= fElementDepth) { + if (DEBUG_CALLBACKS) { + System.out.println("startDocument()"); + } + XMLLocator locator = HTMLScanner.this; + String encoding = fIANAEncoding; + Augmentations augs = locationAugs(); + NamespaceContext nscontext = new NamespaceSupport(); + XercesBridge.getInstance().XMLDocumentHandler_startDocument(fDocumentHandler, locator, encoding, nscontext, augs); + } + if (fInsertDoctype && fDocumentHandler != null) { + String root = HTMLElements.getElement(HTMLElements.HTML).name; + root = modifyName(root, fNamesElems); + String pubid = fDoctypePubid; + String sysid = fDoctypeSysid; + fDocumentHandler.doctypeDecl(root, pubid, sysid, + synthesizedAugs()); + } + setScannerState(STATE_CONTENT); + break; + } + case STATE_END_DOCUMENT: { + if (fDocumentHandler != null && fElementCount >= fElementDepth && complete) { + if (DEBUG_CALLBACKS) { + System.out.println("endDocument()"); + } + fEndLineNumber = fCurrentEntity.getLineNumber(); + fEndColumnNumber = fCurrentEntity.getColumnNumber(); + fEndCharacterOffset = fCurrentEntity.getCharacterOffset(); + fDocumentHandler.endDocument(locationAugs()); + } + return false; + } + default: { + throw new RuntimeException("unknown scanner state: "+fScannerState); + } + } + } + catch (EOFException e) { + if (fCurrentEntityStack.empty()) { + setScannerState(STATE_END_DOCUMENT); + } + else { + fCurrentEntity = (CurrentEntity)fCurrentEntityStack.pop(); + } + next = true; + } + } while (next || complete); + return true; + } // scan(boolean):boolean + + /** + * Scans the content of

+ * If the encoding is changed, then the scanner calls the + * playback method and re-scans the beginning of the HTML + * document again. This should not be too much of a performance problem + * because the <meta> tag appears at the beginning of the document. + *

+ * If the <body> tag is reached without playing back the bytes, + * then the buffer can be cleared by calling the clear + * method. This stops the buffering of bytes and allows the memory used + * by the buffer to be reclaimed. + *

+ * Note: + * If the buffer is never played back or cleared, this input stream + * will continue to buffer the entire stream. Therefore, it is very + * important to use this stream correctly. + * + * @author Andy Clark + */ + public static class PlaybackInputStream + extends FilterInputStream { + + // + // Constants + // + + /** Set to true to debug playback. */ + private static final boolean DEBUG_PLAYBACK = false; + + // + // Data + // + + // state + + /** Playback mode. */ + protected boolean fPlayback = false; + + /** Buffer cleared. */ + protected boolean fCleared = false; + + /** Encoding detected. */ + protected boolean fDetected = false; + + // buffer info + + /** Byte buffer. */ + protected byte[] fByteBuffer = new byte[1024]; + + /** Offset into byte buffer during playback. */ + protected int fByteOffset = 0; + + /** Length of bytes read into byte buffer. */ + protected int fByteLength = 0; + + /** Pushback offset. */ + public int fPushbackOffset = 0; + + /** Pushback length. */ + public int fPushbackLength = 0; + + // + // Constructors + // + + /** Constructor. */ + public PlaybackInputStream(InputStream in) { + super(in); + } // (InputStream) + + // + // Public methods + // + + /** Detect encoding. */ + public void detectEncoding(String[] encodings) throws IOException { + if (fDetected) { + throw new IOException("Should not detect encoding twice."); + } + fDetected = true; + int b1 = read(); + if (b1 == -1) { + return; + } + int b2 = read(); + if (b2 == -1) { + fPushbackLength = 1; + return; + } + // UTF-8 BOM: 0xEFBBBF + if (b1 == 0xEF && b2 == 0xBB) { + int b3 = read(); + if (b3 == 0xBF) { + fPushbackOffset = 3; + encodings[0] = "UTF-8"; + encodings[1] = "UTF8"; + return; + } + fPushbackLength = 3; + } + // UTF-16 LE BOM: 0xFFFE + if (b1 == 0xFF && b2 == 0xFE) { + encodings[0] = "UTF-16"; + encodings[1] = "UnicodeLittleUnmarked"; + return; + } + // UTF-16 BE BOM: 0xFEFF + else if (b1 == 0xFE && b2 == 0xFF) { + encodings[0] = "UTF-16"; + encodings[1] = "UnicodeBigUnmarked"; + return; + } + // unknown + fPushbackLength = 2; + } // detectEncoding() + + /** Playback buffer contents. */ + public void playback() { + fPlayback = true; + } // playback() + + /** + * Clears the buffer. + *

+ * Note: + * The buffer cannot be cleared during playback. Therefore, calling + * this method during playback will not do anything. However, the + * buffer will be cleared automatically at the end of playback. + */ + public void clear() { + if (!fPlayback) { + fCleared = true; + fByteBuffer = null; + } + } // clear() + + // + // InputStream methods + // + + /** Read a byte. */ + public int read() throws IOException { + if (DEBUG_PLAYBACK) { + System.out.println("(read"); + } + if (fPushbackOffset < fPushbackLength) { + return fByteBuffer[fPushbackOffset++]; + } + if (fCleared) { + return in.read(); + } + if (fPlayback) { + int c = fByteBuffer[fByteOffset++]; + if (fByteOffset == fByteLength) { + fCleared = true; + fByteBuffer = null; + } + if (DEBUG_PLAYBACK) { + System.out.println(")read -> "+(char)c); + } + return c; + } + int c = in.read(); + if (c != -1) { + if (fByteLength == fByteBuffer.length) { + byte[] newarray = new byte[fByteLength + 1024]; + System.arraycopy(fByteBuffer, 0, newarray, 0, fByteLength); + fByteBuffer = newarray; + } + fByteBuffer[fByteLength++] = (byte)c; + } + if (DEBUG_PLAYBACK) { + System.out.println(")read -> "+(char)c); + } + return c; + } // read():int + + /** Read an array of bytes. */ + public int read(byte[] array) throws IOException { + return read(array, 0, array.length); + } // read(byte[]):int + + /** Read an array of bytes. */ + public int read(byte[] array, int offset, int length) throws IOException { + if (DEBUG_PLAYBACK) { + System.out.println(")read("+offset+','+length+')'); + } + if (fPushbackOffset < fPushbackLength) { + int count = fPushbackLength - fPushbackOffset; + if (count > length) { + count = length; + } + System.arraycopy(fByteBuffer, fPushbackOffset, array, offset, count); + fPushbackOffset += count; + return count; + } + if (fCleared) { + return in.read(array, offset, length); + } + if (fPlayback) { + if (fByteOffset + length > fByteLength) { + length = fByteLength - fByteOffset; + } + System.arraycopy(fByteBuffer, fByteOffset, array, offset, length); + fByteOffset += length; + if (fByteOffset == fByteLength) { + fCleared = true; + fByteBuffer = null; + } + return length; + } + int count = in.read(array, offset, length); + if (count != -1) { + if (fByteLength + count > fByteBuffer.length) { + byte[] newarray = new byte[fByteLength + count + 512]; + System.arraycopy(fByteBuffer, 0, newarray, 0, fByteLength); + fByteBuffer = newarray; + } + System.arraycopy(array, offset, fByteBuffer, fByteLength, count); + fByteLength += count; + } + if (DEBUG_PLAYBACK) { + System.out.println(")read("+offset+','+length+") -> "+count); + } + return count; + } // read(byte[]):int + + } // class PlaybackInputStream + + /** + * Location infoset item. + * + * @author Andy Clark + */ + protected static class LocationItem implements HTMLEventInfo, Cloneable { + + // + // Data + // + + /** Beginning line number. */ + protected int fBeginLineNumber; + + /** Beginning column number. */ + protected int fBeginColumnNumber; + + /** Beginning character offset. */ + protected int fBeginCharacterOffset; + + /** Ending line number. */ + protected int fEndLineNumber; + + /** Ending column number. */ + protected int fEndColumnNumber; + + /** Ending character offset. */ + protected int fEndCharacterOffset; + + // + // Public methods + // + public LocationItem() { + // nothing + } + + LocationItem(final LocationItem other) { + setValues(other.fBeginLineNumber, other.fBeginColumnNumber, other.fBeginCharacterOffset, + other.fEndLineNumber, other.fEndColumnNumber, other.fEndCharacterOffset); + } + + /** Sets the values of this item. */ + public void setValues(int beginLine, int beginColumn, int beginOffset, + int endLine, int endColumn, int endOffset) { + fBeginLineNumber = beginLine; + fBeginColumnNumber = beginColumn; + fBeginCharacterOffset = beginOffset; + fEndLineNumber = endLine; + fEndColumnNumber = endColumn; + fEndCharacterOffset = endOffset; + } // setValues(int,int,int,int) + + // + // HTMLEventInfo methods + // + + // location information + + /** Returns the line number of the beginning of this event.*/ + public int getBeginLineNumber() { + return fBeginLineNumber; + } // getBeginLineNumber():int + + /** Returns the column number of the beginning of this event.*/ + public int getBeginColumnNumber() { + return fBeginColumnNumber; + } // getBeginColumnNumber():int + + /** Returns the character offset of the beginning of this event.*/ + public int getBeginCharacterOffset() { + return fBeginCharacterOffset; + } // getBeginCharacterOffset():int + + /** Returns the line number of the end of this event.*/ + public int getEndLineNumber() { + return fEndLineNumber; + } // getEndLineNumber():int + + /** Returns the column number of the end of this event.*/ + public int getEndColumnNumber() { + return fEndColumnNumber; + } // getEndColumnNumber():int + + /** Returns the character offset of the end of this event.*/ + public int getEndCharacterOffset() { + return fEndCharacterOffset; + } // getEndCharacterOffset():int + + // other information + + /** Returns true if this corresponding event was synthesized. */ + public boolean isSynthesized() { + return false; + } // isSynthesize():boolean + + // + // Object methods + // + + /** Returns a string representation of this object. */ + public String toString() { + StringBuffer str = new StringBuffer(); + str.append(fBeginLineNumber); + str.append(':'); + str.append(fBeginColumnNumber); + str.append(':'); + str.append(fBeginCharacterOffset); + str.append(':'); + str.append(fEndLineNumber); + str.append(':'); + str.append(fEndColumnNumber); + str.append(':'); + str.append(fEndCharacterOffset); + return str.toString(); + } // toString():String + + } // class LocationItem + + /** + * To detect if 2 encoding are compatible, both must be able to read the meta tag specifying + * the new encoding. This means that the byte representation of some minimal html markup must + * be the same in both encodings + */ + boolean isEncodingCompatible(final String encoding1, final String encoding2) { + try { + try { + return canRoundtrip(encoding1, encoding2); + } + catch (final UnsupportedOperationException e) { + // if encoding1 only supports decode, we can test it the other way to only decode with it + try { + return canRoundtrip(encoding2, encoding1); + } + catch (final UnsupportedOperationException e1) { + // encoding2 only supports decode too. Time to give up. + return false; + } + } + } + catch (final UnsupportedEncodingException e) { + return false; + } + } + + private boolean canRoundtrip(final String encodeCharset, final String decodeCharset) throws UnsupportedEncodingException { + final String reference = " -1"); + } + return -1; + } + } + final char c = fCurrentEntity.getNextChar(); + if (DEBUG_BUFFER) { + fCurrentEntity.debugBufferIfNeeded(")read: ", " -> " + c); + } + return c; + } // readPreservingBufferContent():int + + /** + * Indicates if the end comment --> is available, loading further data if needed, without to reset the buffer + */ + private boolean endCommentAvailable() throws IOException { + int nbCaret = 0; + final int originalOffset = fCurrentEntity.offset; + final int originalColumnNumber = fCurrentEntity.getColumnNumber(); + final int originalCharacterOffset = fCurrentEntity.getCharacterOffset(); + + while (true) { + int c = readPreservingBufferContent(); + if (c == -1) { + fCurrentEntity.restorePosition(originalOffset, originalColumnNumber, originalCharacterOffset); + return false; + } + else if (c == '>' && nbCaret >= 2) { + fCurrentEntity.restorePosition(originalOffset, originalColumnNumber, originalCharacterOffset); + return true; + } + else if (c == '-') { + nbCaret++; + } + else { + nbCaret = 0; + } + } + } + + /** + * Reduces the buffer to the content between start and end marker when + * only whitespaces are found before the startMarker as well as after the end marker + */ + static void reduceToContent(final XMLStringBuffer buffer, final String startMarker, final String endMarker) { + int i = 0; + int startContent = -1; + final int l1 = startMarker.length(); + final int l2 = endMarker.length(); + while (i < buffer.length - l1 - l2) { + final char c = buffer.ch[buffer.offset+i]; + if (Character.isWhitespace(c)) { + ++i; + } + else if (c == startMarker.charAt(0) + && startMarker.equals(new String(buffer.ch, buffer.offset+i, l1))) { + startContent = buffer.offset + i + l1; + break; + } + else { + return; // start marker not found + } + } + if (startContent == -1) { // start marker not found + return; + } + + i = buffer.length - 1; + while (i > startContent + l2) { + final char c = buffer.ch[buffer.offset+i]; + if (Character.isWhitespace(c)) { + --i; + } + else if (c == endMarker.charAt(l2-1) + && endMarker.equals(new String(buffer.ch, buffer.offset+i-l2+1, l2))) { + + buffer.length = buffer.offset + i - startContent - 2; + buffer.offset = startContent; + return; + } + else { + return; // start marker not found + } + } + } +} // class HTMLScanner diff --git a/src/main/java/org/cyberneko/html/HTMLTagBalancer.java b/src/main/java/org/cyberneko/html/HTMLTagBalancer.java new file mode 100644 index 0000000..52b8668 --- /dev/null +++ b/src/main/java/org/cyberneko/html/HTMLTagBalancer.java @@ -0,0 +1,1452 @@ +/* + * Copyright 2002-2009 Andy Clark, Marc Guillemot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyberneko.html; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.xerces.util.XMLAttributesImpl; +import org.apache.xerces.xni.Augmentations; +import org.apache.xerces.xni.NamespaceContext; +import org.apache.xerces.xni.QName; +import org.apache.xerces.xni.XMLAttributes; +import org.apache.xerces.xni.XMLDocumentHandler; +import org.apache.xerces.xni.XMLLocator; +import org.apache.xerces.xni.XMLResourceIdentifier; +import org.apache.xerces.xni.XMLString; +import org.apache.xerces.xni.XNIException; +import org.apache.xerces.xni.parser.XMLComponentManager; +import org.apache.xerces.xni.parser.XMLConfigurationException; +import org.apache.xerces.xni.parser.XMLDocumentFilter; +import org.apache.xerces.xni.parser.XMLDocumentSource; +import org.cyberneko.html.HTMLElements.Element; +import org.cyberneko.html.filters.NamespaceBinder; +import org.cyberneko.html.xercesbridge.XercesBridge; + +/** + * Balances tags in an HTML document. This component receives document events + * and tries to correct many common mistakes that human (and computer) HTML + * document authors make. This tag balancer can: + *

    + *
  • add missing parent elements; + *
  • automatically close elements with optional end tags; and + *
  • handle mis-matched inline element tags. + *
+ *

+ * This component recognizes the following features: + *

    + *
  • http://cyberneko.org/html/features/augmentations + *
  • http://cyberneko.org/html/features/report-errors + *
  • http://cyberneko.org/html/features/balance-tags/document-fragment + *
  • http://cyberneko.org/html/features/balance-tags/ignore-outside-content + *
+ *

+ * This component recognizes the following properties: + *

    + *
  • http://cyberneko.org/html/properties/names/elems + *
  • http://cyberneko.org/html/properties/names/attrs + *
  • http://cyberneko.org/html/properties/error-reporter + *
  • http://cyberneko.org/html/properties/balance-tags/current-stack + *
+ * + * @see HTMLElements + * + * @author Andy Clark + * @author Marc Guillemot + * + * @version $Id: HTMLTagBalancer.java,v 1.20 2005/02/14 04:06:22 andyc Exp $ + */ +public class HTMLTagBalancer + implements XMLDocumentFilter, HTMLComponent { + + // + // Constants + // + + // features + + /** Namespaces. */ + protected static final String NAMESPACES = "http://xml.org/sax/features/namespaces"; + + /** Include infoset augmentations. */ + protected static final String AUGMENTATIONS = "http://cyberneko.org/html/features/augmentations"; + + /** Report errors. */ + protected static final String REPORT_ERRORS = "http://cyberneko.org/html/features/report-errors"; + + /** Document fragment balancing only (deprecated). */ + protected static final String DOCUMENT_FRAGMENT_DEPRECATED = "http://cyberneko.org/html/features/document-fragment"; + + /** Document fragment balancing only. */ + protected static final String DOCUMENT_FRAGMENT = "http://cyberneko.org/html/features/balance-tags/document-fragment"; + + /** Ignore outside content. */ + protected static final String IGNORE_OUTSIDE_CONTENT = "http://cyberneko.org/html/features/balance-tags/ignore-outside-content"; + + /** Recognized features. */ + private static final String[] RECOGNIZED_FEATURES = { + NAMESPACES, + AUGMENTATIONS, + REPORT_ERRORS, + DOCUMENT_FRAGMENT_DEPRECATED, + DOCUMENT_FRAGMENT, + IGNORE_OUTSIDE_CONTENT, + }; + + /** Recognized features defaults. */ + private static final Boolean[] RECOGNIZED_FEATURES_DEFAULTS = { + null, + null, + null, + null, + Boolean.FALSE, + Boolean.FALSE, + }; + + // properties + + /** Modify HTML element names: { "upper", "lower", "default" }. */ + protected static final String NAMES_ELEMS = "http://cyberneko.org/html/properties/names/elems"; + + /** Modify HTML attribute names: { "upper", "lower", "default" }. */ + protected static final String NAMES_ATTRS = "http://cyberneko.org/html/properties/names/attrs"; + + /** Error reporter. */ + protected static final String ERROR_REPORTER = "http://cyberneko.org/html/properties/error-reporter"; + + /** + * EXPERIMENTAL: may change in next release
+ * Name of the property holding the stack of elements in which context a document fragment should be parsed. + **/ + public static final String FRAGMENT_CONTEXT_STACK = "http://cyberneko.org/html/properties/balance-tags/fragment-context-stack"; + + /** Recognized properties. */ + private static final String[] RECOGNIZED_PROPERTIES = { + NAMES_ELEMS, + NAMES_ATTRS, + ERROR_REPORTER, + FRAGMENT_CONTEXT_STACK, + }; + + /** Recognized properties defaults. */ + private static final Object[] RECOGNIZED_PROPERTIES_DEFAULTS = { + null, + null, + null, + null, + }; + + // modify HTML names + + /** Don't modify HTML names. */ + protected static final short NAMES_NO_CHANGE = 0; + + /** Match HTML element names. */ + protected static final short NAMES_MATCH = 0; + + /** Uppercase HTML names. */ + protected static final short NAMES_UPPERCASE = 1; + + /** Lowercase HTML names. */ + protected static final short NAMES_LOWERCASE = 2; + + // static vars + + /** Synthesized event info item. */ + protected static final HTMLEventInfo SYNTHESIZED_ITEM = + new HTMLEventInfo.SynthesizedItem(); + + // + // Data + // + + // features + + /** Namespaces. */ + protected boolean fNamespaces; + + /** Include infoset augmentations. */ + protected boolean fAugmentations; + + /** Report errors. */ + protected boolean fReportErrors; + + /** Document fragment balancing only. */ + protected boolean fDocumentFragment; + + /** Ignore outside content. */ + protected boolean fIgnoreOutsideContent; + + /** Allows self closing iframe tags. */ + protected boolean fAllowSelfclosingIframe; + + /** Allows self closing tags. */ + protected boolean fAllowSelfclosingTags; + + // properties + + /** Modify HTML element names. */ + protected short fNamesElems; + + /** Modify HTML attribute names. */ + protected short fNamesAttrs; + + /** Error reporter. */ + protected HTMLErrorReporter fErrorReporter; + + // connections + + /** The document source. */ + protected XMLDocumentSource fDocumentSource; + + /** The document handler. */ + protected XMLDocumentHandler fDocumentHandler; + + // state + + /** The element stack. */ + protected final InfoStack fElementStack = new InfoStack(); + + /** The inline stack. */ + protected final InfoStack fInlineStack = new InfoStack(); + + /** True if seen anything. Important for xml declaration. */ + protected boolean fSeenAnything; + + /** True if root element has been seen. */ + protected boolean fSeenDoctype; + + /** True if root element has been seen. */ + protected boolean fSeenRootElement; + + /** + * True if seen the end of the document element. In other words, + * this variable is set to false until the end </HTML> + * tag is seen (or synthesized). This is used to ensure that + * extraneous events after the end of the document element do not + * make the document stream ill-formed. + */ + protected boolean fSeenRootElementEnd; + + /** True if seen <head< element. */ + protected boolean fSeenHeadElement; + + /** True if seen <body< element. */ + protected boolean fSeenBodyElement; + private boolean fSeenBodyElementEnd; + + /** True if seen <frameset< element. */ + private boolean fSeenFramesetElement; + + /** True if a form is in the stack (allow to discard opening of nested forms) */ + protected boolean fOpenedForm; + + // temp vars + + /** A qualified name. */ + private final QName fQName = new QName(); + + /** Empty attributes. */ + private final XMLAttributes fEmptyAttrs = new XMLAttributesImpl(); + + /** Augmentations. */ + private final HTMLAugmentations fInfosetAugs = new HTMLAugmentations(); + + protected HTMLTagBalancingListener tagBalancingListener; + private LostText lostText_ = new LostText(); + + private boolean forcedStartElement_ = false; + private boolean forcedEndElement_ = false; + + /** + * Stack of elements determining the context in which a document fragment should be parsed + */ + private QName[] fragmentContextStack_ = null; + private int fragmentContextStackSize_ = 0; // not 0 only when a fragment is parsed and fragmentContextStack_ is set + + private List/*ElementEntry*/ endElementsBuffer_ = new ArrayList(); + + // + // HTMLComponent methods + // + + /** Returns the default state for a feature. */ + public Boolean getFeatureDefault(String featureId) { + int length = RECOGNIZED_FEATURES != null ? RECOGNIZED_FEATURES.length : 0; + for (int i = 0; i < length; i++) { + if (RECOGNIZED_FEATURES[i].equals(featureId)) { + return RECOGNIZED_FEATURES_DEFAULTS[i]; + } + } + return null; + } // getFeatureDefault(String):Boolean + + /** Returns the default state for a property. */ + public Object getPropertyDefault(String propertyId) { + int length = RECOGNIZED_PROPERTIES != null ? RECOGNIZED_PROPERTIES.length : 0; + for (int i = 0; i < length; i++) { + if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) { + return RECOGNIZED_PROPERTIES_DEFAULTS[i]; + } + } + return null; + } // getPropertyDefault(String):Object + + // + // XMLComponent methods + // + + /** Returns recognized features. */ + public String[] getRecognizedFeatures() { + return RECOGNIZED_FEATURES; + } // getRecognizedFeatures():String[] + + /** Returns recognized properties. */ + public String[] getRecognizedProperties() { + return RECOGNIZED_PROPERTIES; + } // getRecognizedProperties():String[] + + /** Resets the component. */ + public void reset(final XMLComponentManager manager) + throws XMLConfigurationException { + + // get features + fNamespaces = manager.getFeature(NAMESPACES); + fAugmentations = manager.getFeature(AUGMENTATIONS); + fReportErrors = manager.getFeature(REPORT_ERRORS); + fDocumentFragment = manager.getFeature(DOCUMENT_FRAGMENT) || + manager.getFeature(DOCUMENT_FRAGMENT_DEPRECATED); + fIgnoreOutsideContent = manager.getFeature(IGNORE_OUTSIDE_CONTENT); + fAllowSelfclosingIframe = manager.getFeature(HTMLScanner.ALLOW_SELFCLOSING_IFRAME); + fAllowSelfclosingTags = manager.getFeature(HTMLScanner.ALLOW_SELFCLOSING_TAGS); + + // get properties + fNamesElems = getNamesValue(String.valueOf(manager.getProperty(NAMES_ELEMS))); + fNamesAttrs = getNamesValue(String.valueOf(manager.getProperty(NAMES_ATTRS))); + fErrorReporter = (HTMLErrorReporter)manager.getProperty(ERROR_REPORTER); + + fragmentContextStack_ = (QName[]) manager.getProperty(FRAGMENT_CONTEXT_STACK); + fSeenAnything = false; + fSeenDoctype = false; + fSeenRootElement = false; + fSeenRootElementEnd = false; + fSeenHeadElement = false; + fSeenBodyElement = false; + fSeenBodyElementEnd = false; + fSeenFramesetElement = false; + + } // reset(XMLComponentManager) + + /** Sets a feature. */ + public void setFeature(String featureId, boolean state) + throws XMLConfigurationException { + + if (featureId.equals(AUGMENTATIONS)) { + fAugmentations = state; + return; + } + if (featureId.equals(REPORT_ERRORS)) { + fReportErrors = state; + return; + } + if (featureId.equals(IGNORE_OUTSIDE_CONTENT)) { + fIgnoreOutsideContent = state; + return; + } + + } // setFeature(String,boolean) + + /** Sets a property. */ + public void setProperty(String propertyId, Object value) + throws XMLConfigurationException { + + if (propertyId.equals(NAMES_ELEMS)) { + fNamesElems = getNamesValue(String.valueOf(value)); + return; + } + + if (propertyId.equals(NAMES_ATTRS)) { + fNamesAttrs = getNamesValue(String.valueOf(value)); + return; + } + + } // setProperty(String,Object) + + // + // XMLDocumentSource methods + // + + /** Sets the document handler. */ + public void setDocumentHandler(XMLDocumentHandler handler) { + fDocumentHandler = handler; + } // setDocumentHandler(XMLDocumentHandler) + + // @since Xerces 2.1.0 + + /** Returns the document handler. */ + public XMLDocumentHandler getDocumentHandler() { + return fDocumentHandler; + } // getDocumentHandler():XMLDocumentHandler + + // + // XMLDocumentHandler methods + // + + // since Xerces-J 2.2.0 + + /** Start document. */ + public void startDocument(XMLLocator locator, String encoding, + NamespaceContext nscontext, Augmentations augs) + throws XNIException { + + // reset state + fElementStack.top = 0; + if (fragmentContextStack_ != null) { + fragmentContextStackSize_ = fragmentContextStack_.length; + for (int i=0; i and have been buffered to consider outside content + fIgnoreOutsideContent = true; // endElement should not ignore the elements passed from buffer + consumeBufferedEndElements(); + + // handle empty document + if (!fSeenRootElement && !fDocumentFragment) { + if (fReportErrors) { + fErrorReporter.reportError("HTML2000", null); + } + if (fDocumentHandler != null) { + fSeenRootElementEnd = false; + forceStartBody(); // will force and + final String body = modifyName("body", fNamesElems); + fQName.setValues(null, body, body, null); + callEndElement(fQName, synthesizedAugs()); + + final String ename = modifyName("html", fNamesElems); + fQName.setValues(null, ename, ename, null); + callEndElement(fQName, synthesizedAugs()); + } + } + + // pop all remaining elements + else { + int length = fElementStack.top - fragmentContextStackSize_; + for (int i = 0; i < length; i++) { + Info info = fElementStack.pop(); + if (fReportErrors) { + String ename = info.qname.rawname; + fErrorReporter.reportWarning("HTML2001", new Object[]{ename}); + } + if (fDocumentHandler != null) { + callEndElement(info.qname, synthesizedAugs()); + } + } + } + + // call handler + if (fDocumentHandler != null) { + fDocumentHandler.endDocument(augs); + } + + } // endDocument(Augmentations) + + /** + * Consume elements that have been buffered, like that are first consumed + * at the end of document + */ + private void consumeBufferedEndElements() { + final List toConsume = new ArrayList(endElementsBuffer_); + endElementsBuffer_.clear(); + for (int i=0; i if none was present + if (!fSeenHeadElement) { + final QName head = createQName("head"); + forceStartElement(head, null, synthesizedAugs()); + endElement(head, synthesizedAugs()); + } + consumeBufferedEndElements(); // (if any) has been buffered + fSeenFramesetElement = true; + } + else if (elementCode == HTMLElements.BODY) { + // create if none was present + if (!fSeenHeadElement) { + final QName head = createQName("head"); + forceStartElement(head, null, synthesizedAugs()); + endElement(head, synthesizedAugs()); + } + consumeBufferedEndElements(); // (if any) has been buffered + + if (fSeenBodyElement) { + notifyDiscardedStartElement(elem, attrs, augs); + return; + } + fSeenBodyElement = true; + } + else if (elementCode == HTMLElements.FORM) { + if (fOpenedForm) { + notifyDiscardedStartElement(elem, attrs, augs); + return; + } + fOpenedForm = true; + } + else if (elementCode == HTMLElements.UNKNOWN) { + consumeBufferedEndElements(); + } + + // check proper parent + if (element.parent != null) { + final HTMLElements.Element preferedParent = element.parent[0]; + if (fDocumentFragment && (preferedParent.code == HTMLElements.HEAD || preferedParent.code == HTMLElements.BODY)) { + // nothing, don't force HEAD or BODY creation for a document fragment + } + else if (!fSeenRootElement && !fDocumentFragment) { + String pname = preferedParent.name; + pname = modifyName(pname, fNamesElems); + if (fReportErrors) { + String ename = elem.rawname; + fErrorReporter.reportWarning("HTML2002", new Object[]{ename,pname}); + } + final QName qname = new QName(null, pname, pname, null); + final boolean parentCreated = forceStartElement(qname, null, synthesizedAugs()); + if (!parentCreated) { + if (!isForcedCreation) { + notifyDiscardedStartElement(elem, attrs, augs); + } + return; + } + } + else { + if (preferedParent.code != HTMLElements.HEAD || (!fSeenBodyElement && !fDocumentFragment)) { + int depth = getParentDepth(element.parent, element.bounds); + if (depth == -1) { // no parent found + final String pname = modifyName(preferedParent.name, fNamesElems); + final QName qname = new QName(null, pname, pname, null); + if (fReportErrors) { + String ename = elem.rawname; + fErrorReporter.reportWarning("HTML2004", new Object[]{ename,pname}); + } + final boolean parentCreated = forceStartElement(qname, null, synthesizedAugs()); + if (!parentCreated) { + if (!isForcedCreation) { + notifyDiscardedStartElement(elem, attrs, augs); + } + return; + } + } + } + } + } + + // if block element, save immediate parent inline elements + int depth = 0; + if (element.flags == 0) { + int length = fElementStack.top; + fInlineStack.top = 0; + for (int i = length - 1; i >= 0; i--) { + Info info = fElementStack.data[i]; + if (!info.element.isInline()) { + break; + } + fInlineStack.push(info); + endElement(info.qname, synthesizedAugs()); + } + depth = fInlineStack.top; + } + + // close previous elements + // all elements close a